From 1f40445577316ae2f2df53c09147fe254cbe9772 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 19 Aug 2018 00:03:11 +0200 Subject: [PATCH] Actually send the MJPEG frames on to the HTTP stream. --- jpeg_frame_view.cpp | 2 - jpeg_frame_view.h | 2 + mux.cpp | 9 ++++- mux.h | 1 + player.cpp | 92 +++++++++++++++++++++++++++++++++++++++++++++ player.h | 13 +++++++ 6 files changed, 115 insertions(+), 4 deletions(-) diff --git a/jpeg_frame_view.cpp b/jpeg_frame_view.cpp index 9624352..16224ad 100644 --- a/jpeg_frame_view.cpp +++ b/jpeg_frame_view.cpp @@ -20,8 +20,6 @@ using namespace movit; using namespace std; -string filename_for_frame(unsigned stream_idx, int64_t pts); - struct JPEGID { unsigned stream_idx; int64_t pts; diff --git a/jpeg_frame_view.h b/jpeg_frame_view.h index 6f37bfc..277241b 100644 --- a/jpeg_frame_view.h +++ b/jpeg_frame_view.h @@ -11,6 +11,8 @@ #include +std::string filename_for_frame(unsigned stream_idx, int64_t pts); + struct Frame { std::unique_ptr y, cb, cr; unsigned width, height; diff --git a/mux.cpp b/mux.cpp index 37bf321..2f682c9 100644 --- a/mux.cpp +++ b/mux.cpp @@ -58,10 +58,12 @@ Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const avstream_video->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; if (video_codec == CODEC_H264) { avstream_video->codecpar->codec_id = AV_CODEC_ID_H264; - } else { - assert(video_codec == CODEC_NV12); + } else if (video_codec == CODEC_NV12) { avstream_video->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; avstream_video->codecpar->codec_tag = avcodec_pix_fmt_to_codec_tag(AV_PIX_FMT_NV12); + } else { + assert(video_codec == CODEC_MJPEG); + avstream_video->codecpar->codec_id = AV_CODEC_ID_MJPEG; } avstream_video->codecpar->width = width; avstream_video->codecpar->height = height; @@ -86,6 +88,8 @@ Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const memcpy(avstream_video->codecpar->extradata, video_extradata.data(), video_extradata.size()); } + avstream_audio = nullptr; +#if 0 avstream_audio = avformat_new_stream(avctx, nullptr); if (avstream_audio == nullptr) { fprintf(stderr, "avformat_new_stream() failed\n"); @@ -96,6 +100,7 @@ Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const fprintf(stderr, "avcodec_parameters_copy() failed\n"); exit(1); } +#endif AVDictionary *options = NULL; vector> opts = MUX_OPTS; diff --git a/mux.h b/mux.h index 9614bff..53e5539 100644 --- a/mux.h +++ b/mux.h @@ -42,6 +42,7 @@ public: enum Codec { CODEC_H264, CODEC_NV12, // Uncompressed 4:2:0. + CODEC_MJPEG }; enum WriteStrategy { // add_packet() will write the packet immediately, unless plugged. diff --git a/player.cpp b/player.cpp index 9f524bb..e399891 100644 --- a/player.cpp +++ b/player.cpp @@ -5,9 +5,19 @@ #include #include +#include + +extern "C" { +#include +#include +} + #include "clip_list.h" #include "defs.h" +#include "ffmpeg_raii.h" +#include "httpd.h" #include "jpeg_frame_view.h" +#include "mux.h" #include "player.h" using namespace std; @@ -15,6 +25,30 @@ using namespace std::chrono; extern mutex frame_mu; extern vector frames[MAX_STREAMS]; +extern HTTPD *global_httpd; + +namespace { + +string read_file(const string &filename) +{ + FILE *fp = fopen(filename.c_str(), "rb"); + if (fp == nullptr) { + perror(filename.c_str()); + return ""; + } + + fseek(fp, 0, SEEK_END); + long len = ftell(fp); + rewind(fp); + + string ret; + ret.resize(len); + fread(&ret[0], len, 1, fp); + fclose(fp); + return ret; +} + +} // namespace void Player::thread_func() { @@ -76,6 +110,17 @@ void Player::thread_func() destination->setFrame(stream_idx, next_pts); + // Send the frame to the stream. + // FIXME: Vaguely less crazy pts, perhaps. + double pts_float = fmod(duration(next_frame_start.time_since_epoch()).count(), 86400.0f); + int64_t pts = lrint(pts_float * TIMEBASE); + string jpeg = read_file(filename_for_frame(stream_idx, next_pts)); + AVPacket pkt; + av_init_packet(&pkt); + pkt.stream_index = 0; + pkt.data = (uint8_t *)jpeg.data(); + pkt.size = jpeg.size(); + stream_mux->add_packet(pkt, pts, pts); } { @@ -91,6 +136,7 @@ void Player::thread_func() Player::Player(JPEGFrameView *destination) : destination(destination) { + open_output_stream(); thread(&Player::thread_func, this).detach(); } @@ -151,3 +197,49 @@ void Player::override_angle(unsigned stream_idx) } destination->setFrame(stream_idx, *it); } + +void Player::open_output_stream() +{ + AVFormatContext *avctx = avformat_alloc_context(); + avctx->oformat = av_guess_format("nut", nullptr, nullptr); + + uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE); + avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, this, nullptr, nullptr, nullptr); + avctx->pb->write_data_type = &Player::write_packet2_thunk; + avctx->pb->ignore_boundary_point = 1; + + Mux::Codec video_codec = Mux::CODEC_MJPEG; + + avctx->flags = AVFMT_FLAG_CUSTOM_IO; + + string video_extradata; + + constexpr int width = 1280, height = 720; // Doesn't matter for MJPEG. + stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, /*audio_codec_parameters=*/nullptr, COARSE_TIMEBASE, + /*write_callback=*/nullptr, Mux::WRITE_FOREGROUND, {})); +} + +int Player::write_packet2_thunk(void *opaque, uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time) +{ + Player *player = (Player *)opaque; + return player->write_packet2(buf, buf_size, type, time); +} + +int Player::write_packet2(uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time) +{ + if (type == AVIO_DATA_MARKER_SYNC_POINT || type == AVIO_DATA_MARKER_BOUNDARY_POINT) { + seen_sync_markers = true; + } else if (type == AVIO_DATA_MARKER_UNKNOWN && !seen_sync_markers) { + // We don't know if this is a keyframe or not (the muxer could + // avoid marking it), so we just have to make the best of it. + type = AVIO_DATA_MARKER_SYNC_POINT; + } + + if (type == AVIO_DATA_MARKER_HEADER) { + stream_mux_header.append((char *)buf, buf_size); + global_httpd->set_header(stream_mux_header); + } else { + global_httpd->add_data((char *)buf, buf_size, type == AVIO_DATA_MARKER_SYNC_POINT, time, AVRational{ AV_TIME_BASE, 1 }); + } + return buf_size; +} diff --git a/player.h b/player.h index e251c6e..ab8f078 100644 --- a/player.h +++ b/player.h @@ -7,7 +7,12 @@ #include #include +extern "C" { +#include +} + class JPEGFrameView; +class Mux; class Player { public: @@ -23,6 +28,9 @@ public: private: void thread_func(); + void open_output_stream(); + static int write_packet2_thunk(void *opaque, uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time); + int write_packet2(uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time); JPEGFrameView *destination; done_callback_func done_callback; @@ -36,6 +44,11 @@ private: bool new_clip_ready = false; // Under queue_state_mu. bool playing = false; // Under queue_state_mu. int override_stream_idx = -1; // Under queue_state_mu. + + // For streaming. + std::unique_ptr stream_mux; // To HTTP. + std::string stream_mux_header; + bool seen_sync_markers = false; }; #endif // !defined(_PLAYER_H) -- 2.39.2