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;
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");
fprintf(stderr, "avcodec_parameters_copy() failed\n");
exit(1);
}
+#endif
AVDictionary *options = NULL;
vector<pair<string, string>> opts = MUX_OPTS;
#include <thread>
#include <vector>
+#include <stdio.h>
+
+extern "C" {
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+}
+
#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;
extern mutex frame_mu;
extern vector<int64_t> 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()
{
destination->setFrame(stream_idx, next_pts);
+ // Send the frame to the stream.
+ // FIXME: Vaguely less crazy pts, perhaps.
+ double pts_float = fmod(duration<double>(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);
}
{
Player::Player(JPEGFrameView *destination)
: destination(destination)
{
+ open_output_stream();
thread(&Player::thread_func, this).detach();
}
}
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;
+}
#include <functional>
#include <mutex>
+extern "C" {
+#include <libavformat/avio.h>
+}
+
class JPEGFrameView;
+class Mux;
class Player {
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;
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<Mux> stream_mux; // To HTTP.
+ std::string stream_mux_header;
+ bool seen_sync_markers = false;
};
#endif // !defined(_PLAYER_H)