|
@@ -2,6 +2,12 @@
|
|
|
|
|
|
#include <iostream>
|
|
#include <iostream>
|
|
|
|
|
|
|
|
+using alm::VideoExportException;
|
|
|
|
+
|
|
|
|
+VideoExportException::VideoExportException(const std::string& err) :
|
|
|
|
+ std::runtime_error{ err }
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
|
|
|
|
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
|
|
@@ -9,8 +15,6 @@
|
|
#define av_frame_free avcodec_free_frame
|
|
#define av_frame_free avcodec_free_frame
|
|
#endif
|
|
#endif
|
|
|
|
|
|
-const uint8_t VideoStream::endcode[] = { 0, 0, 1, 0xb7 };
|
|
|
|
-
|
|
|
|
|
|
|
|
VideoStream::VideoStream(int width, int height, const std::string& filename, int bitrate, int fps, const char* preset) :
|
|
VideoStream::VideoStream(int width, int height, const std::string& filename, int bitrate, int fps, const char* preset) :
|
|
width{ width & (~1) }, height{ height & (~1) }
|
|
width{ width & (~1) }, height{ height & (~1) }
|
|
@@ -20,21 +24,20 @@ VideoStream::VideoStream(int width, int height, const std::string& filename, int
|
|
|
|
|
|
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
|
|
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
|
|
if (!codec) {
|
|
if (!codec) {
|
|
- fprintf(stderr, "invalid codec\n");
|
|
|
|
- exit(1);
|
|
|
|
|
|
+ throw VideoExportException{ "could not find h264 encoder" };
|
|
}
|
|
}
|
|
|
|
|
|
AVOutputFormat* oformat = av_guess_format(nullptr, filename.c_str(), nullptr);
|
|
AVOutputFormat* oformat = av_guess_format(nullptr, filename.c_str(), nullptr);
|
|
if (!oformat)
|
|
if (!oformat)
|
|
oformat = av_guess_format("mp4", nullptr, nullptr);
|
|
oformat = av_guess_format("mp4", nullptr, nullptr);
|
|
if (oformat == nullptr)
|
|
if (oformat == nullptr)
|
|
- throw "invalid format";
|
|
|
|
|
|
+ throw VideoExportException{ "invalid format" };
|
|
|
|
|
|
codecContext = avcodec_alloc_context3(codec);
|
|
codecContext = avcodec_alloc_context3(codec);
|
|
|
|
|
|
pkt = av_packet_alloc();
|
|
pkt = av_packet_alloc();
|
|
if (!pkt)
|
|
if (!pkt)
|
|
- exit(1);
|
|
|
|
|
|
+ throw VideoExportException{ "could not allocate packet" };
|
|
|
|
|
|
codecContext->bit_rate = bitrate * 1000;
|
|
codecContext->bit_rate = bitrate * 1000;
|
|
codecContext->width = width;
|
|
codecContext->width = width;
|
|
@@ -42,17 +45,19 @@ VideoStream::VideoStream(int width, int height, const std::string& filename, int
|
|
codecContext->time_base = AVRational{ 1, fps };
|
|
codecContext->time_base = AVRational{ 1, fps };
|
|
codecContext->framerate = AVRational{ fps, 1 };
|
|
codecContext->framerate = AVRational{ fps, 1 };
|
|
|
|
|
|
- codecContext->gop_size = 5; /* emit one intra frame every five frames */
|
|
|
|
- codecContext->max_b_frames = 1;
|
|
|
|
|
|
+ codecContext->gop_size = 16; /* one intra frame every 16 frames */
|
|
|
|
+ codecContext->max_b_frames = 3;
|
|
codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
|
|
codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
|
|
|
|
|
|
formatContext = avformat_alloc_context();
|
|
formatContext = avformat_alloc_context();
|
|
|
|
+ if (!formatContext)
|
|
|
|
+ throw VideoExportException{ "error allocating format context" };
|
|
formatContext->oformat = oformat;
|
|
formatContext->oformat = oformat;
|
|
formatContext->video_codec_id = oformat->video_codec;
|
|
formatContext->video_codec_id = oformat->video_codec;
|
|
|
|
|
|
stream = avformat_new_stream(formatContext, codec);
|
|
stream = avformat_new_stream(formatContext, codec);
|
|
if (!stream)
|
|
if (!stream)
|
|
- throw "error";
|
|
|
|
|
|
+ throw VideoExportException{ "error creating stream" };
|
|
|
|
|
|
params = avcodec_parameters_alloc();
|
|
params = avcodec_parameters_alloc();
|
|
avcodec_parameters_from_context(params, codecContext);
|
|
avcodec_parameters_from_context(params, codecContext);
|
|
@@ -71,13 +76,15 @@ VideoStream::VideoStream(int width, int height, const std::string& filename, int
|
|
av_opt_set(codecContext->priv_data, "preset", preset, 0);
|
|
av_opt_set(codecContext->priv_data, "preset", preset, 0);
|
|
|
|
|
|
if (avcodec_open2(codecContext, codec, nullptr) < 0) {
|
|
if (avcodec_open2(codecContext, codec, nullptr) < 0) {
|
|
- fprintf(stderr, "could not open codec\n");
|
|
|
|
- exit(1);
|
|
|
|
|
|
+ throw VideoExportException{ "could not open codec" };
|
|
|
|
+ }
|
|
|
|
+ int opened = avio_open(&formatContext->pb, filename.c_str(), AVIO_FLAG_WRITE);
|
|
|
|
+ if (opened < 0) {
|
|
|
|
+ throw VideoExportException{ std::string("could not open file '") + filename + "'" };
|
|
}
|
|
}
|
|
- avio_open(&formatContext->pb, filename.c_str(), AVIO_FLAG_WRITE);
|
|
|
|
|
|
|
|
- if (avformat_write_header(formatContext, NULL) < 0) {
|
|
|
|
- throw "error";
|
|
|
|
|
|
+ if (avformat_write_header(formatContext, nullptr) < 0) {
|
|
|
|
+ throw VideoExportException{ "error writing header" };
|
|
}
|
|
}
|
|
/*file = fopen(filename.c_str(), "wb");
|
|
/*file = fopen(filename.c_str(), "wb");
|
|
if (!file) {
|
|
if (!file) {
|
|
@@ -86,55 +93,26 @@ VideoStream::VideoStream(int width, int height, const std::string& filename, int
|
|
}*/
|
|
}*/
|
|
|
|
|
|
picture = av_frame_alloc();
|
|
picture = av_frame_alloc();
|
|
- av_frame_make_writable(picture);
|
|
|
|
|
|
+ if (!picture)
|
|
|
|
+ throw VideoExportException{ "error allocating frame" };
|
|
|
|
+ if (av_frame_make_writable(picture) < 0) {
|
|
|
|
+ throw VideoExportException{ "error making frame writeable" };
|
|
|
|
+ }
|
|
picture->format = codecContext->pix_fmt;
|
|
picture->format = codecContext->pix_fmt;
|
|
picture->width = codecContext->width;
|
|
picture->width = codecContext->width;
|
|
picture->height = codecContext->height;
|
|
picture->height = codecContext->height;
|
|
|
|
|
|
int retval = av_frame_get_buffer(picture, 0);
|
|
int retval = av_frame_get_buffer(picture, 0);
|
|
if (retval < 0) {
|
|
if (retval < 0) {
|
|
- fprintf(stderr, "could not alloc the frame data\n");
|
|
|
|
- exit(1);
|
|
|
|
|
|
+ throw VideoExportException{ "could not allocate frame data" };
|
|
}
|
|
}
|
|
//av_image_alloc(picture->data, picture->linesize, width, height, codecContext->pix_fmt, 32);
|
|
//av_image_alloc(picture->data, picture->linesize, width, height, codecContext->pix_fmt, 32);
|
|
|
|
|
|
swsContext = sws_getContext(width, height,
|
|
swsContext = sws_getContext(width, height,
|
|
AV_PIX_FMT_RGB24, width, height,
|
|
AV_PIX_FMT_RGB24, width, height,
|
|
AV_PIX_FMT_YUV420P, 0, 0, 0, 0);
|
|
AV_PIX_FMT_YUV420P, 0, 0, 0, 0);
|
|
-}
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-void VideoStream::encode(AVFrame* frame)
|
|
|
|
-{
|
|
|
|
- int ret;
|
|
|
|
-
|
|
|
|
- /* send the frame to the encoder */
|
|
|
|
- ret = avcodec_send_frame(codecContext, frame);
|
|
|
|
- if (ret < 0) {
|
|
|
|
- fprintf(stderr, "error sending a frame for encoding\n");
|
|
|
|
- exit(1);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- while (ret >= 0) {
|
|
|
|
- ret = avcodec_receive_packet(codecContext, pkt);
|
|
|
|
- //ret = avcodec_encode_video2(codecContext, pkt, picture, &gotPacket);
|
|
|
|
- if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
|
|
|
- return;
|
|
|
|
- else if (ret < 0) {
|
|
|
|
- fprintf(stderr, "error during encoding\n");
|
|
|
|
- exit(1);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- printf("encoded frame %3d\"PRId64\" (size=%5d)\n", pkt->pts, pkt->size);
|
|
|
|
- //fwrite(pkt->data, 1, pkt->size, outfile);
|
|
|
|
- //av_interleaved_write_frame(formatContext, pkt);
|
|
|
|
-
|
|
|
|
- av_packet_rescale_ts(pkt, AVRational{1, 60}, stream->time_base);
|
|
|
|
- pkt->stream_index = stream->index;
|
|
|
|
-
|
|
|
|
- av_write_frame(formatContext, pkt);
|
|
|
|
- av_packet_unref(pkt);
|
|
|
|
- }
|
|
|
|
|
|
+ if (!swsContext)
|
|
|
|
+ throw VideoExportException{ "error preparing sws context" };
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -144,44 +122,13 @@ VideoStream::~VideoStream()
|
|
encode(nullptr);
|
|
encode(nullptr);
|
|
av_write_trailer(this->formatContext);
|
|
av_write_trailer(this->formatContext);
|
|
|
|
|
|
- /* add sequence end code to have a real MPEG file */
|
|
|
|
- //fwrite(endcode, 1, sizeof(endcode), file);
|
|
|
|
- //fclose(file);
|
|
|
|
-
|
|
|
|
avcodec_close(codecContext);
|
|
avcodec_close(codecContext);
|
|
avio_close(formatContext->pb);
|
|
avio_close(formatContext->pb);
|
|
av_frame_unref(picture);
|
|
av_frame_unref(picture);
|
|
- //av_free(codecContext);
|
|
|
|
avcodec_parameters_free(¶ms);
|
|
avcodec_parameters_free(¶ms);
|
|
avcodec_free_context(&codecContext);
|
|
avcodec_free_context(&codecContext);
|
|
av_frame_free(&picture);
|
|
av_frame_free(&picture);
|
|
av_packet_free(&pkt);
|
|
av_packet_free(&pkt);
|
|
-
|
|
|
|
-/*
|
|
|
|
- AVPacket pkt;
|
|
|
|
- av_init_packet(&pkt);
|
|
|
|
- pkt.data = nullptr;
|
|
|
|
- pkt.size = 0;
|
|
|
|
-
|
|
|
|
- for (;;) {
|
|
|
|
- avcodec_send_frame(codecContext, NULL);
|
|
|
|
- if (avcodec_receive_packet(codecContext, &pkt) == 0) {
|
|
|
|
- av_interleaved_write_frame(codecContext, &pkt);
|
|
|
|
- av_packet_unref(&pkt);
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- av_write_trailer();
|
|
|
|
- if (!(oformat->flags & AVFMT_NOFILE)) {
|
|
|
|
- int err = avio_close(ofctx->pb);
|
|
|
|
- if (err < 0) {
|
|
|
|
- Debug("Failed to close file", err);
|
|
|
|
- }
|
|
|
|
- }*/
|
|
|
|
-
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -189,34 +136,9 @@ void VideoStream::addFrame(const Bitmap<RGBColor>& frame)
|
|
{
|
|
{
|
|
int retval = av_frame_make_writable(picture);
|
|
int retval = av_frame_make_writable(picture);
|
|
if (retval < 0)
|
|
if (retval < 0)
|
|
- exit(1);
|
|
|
|
|
|
+ throw VideoExportException{ "could not write to frame data" };
|
|
|
|
|
|
- /* prepare a dummy image */
|
|
|
|
- /* Y */
|
|
|
|
- /*for(int y = 0; y < height; y++) {
|
|
|
|
- for(int x = 0; x < width; x++) {
|
|
|
|
- picture->data[0][y * picture->linesize[0] + x] = frame.get(x, y).r / 2;
|
|
|
|
- }
|
|
|
|
- }*/
|
|
|
|
-
|
|
|
|
- /* Cb and Cr */
|
|
|
|
- /*for(int y=0;y<height / 2;y++) {
|
|
|
|
- for(int x=0;x<width / 2;x++) {
|
|
|
|
- picture->data[1][y * picture->linesize[1] + x] = frame.get(x * 2, y * 2).g / 2;
|
|
|
|
- picture->data[2][y * picture->linesize[2] + x] = frame.get(x * 2, y * 2).b / 2;
|
|
|
|
- }
|
|
|
|
- }*/
|
|
|
|
-
|
|
|
|
- /*auto gammaCorrect = [] (const RGBColor& rgb) {
|
|
|
|
- const float gamma = 2.2f;
|
|
|
|
- return RGBColor {
|
|
|
|
- uint8_t(::powf(rgb.r / 255.0f, 1.0f / gamma) * 255),
|
|
|
|
- uint8_t(::powf(rgb.g / 255.0f, 1.0f / gamma) * 255),
|
|
|
|
- uint8_t(::powf(rgb.b / 255.0f, 1.0f / gamma) * 255),
|
|
|
|
- };
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- Bitmap<RGBColor> gammaCorrected = frame.map<RGBColor>(gammaCorrect);*/
|
|
|
|
|
|
+ /*Bitmap<RGBColor> gammaCorrected = frame.map<RGBColor>(gammaCorrect);*/
|
|
|
|
|
|
const uint8_t* pixelPointer[] = { reinterpret_cast<const uint8_t*>(frame.pixels.get()), 0 };
|
|
const uint8_t* pixelPointer[] = { reinterpret_cast<const uint8_t*>(frame.pixels.get()), 0 };
|
|
const int linesizeIn[] = { int(frame.width * sizeof(RGBColor)) };
|
|
const int linesizeIn[] = { int(frame.width * sizeof(RGBColor)) };
|
|
@@ -230,3 +152,36 @@ void VideoStream::addFrame(const Bitmap<RGBColor>& frame)
|
|
encode(picture);
|
|
encode(picture);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
|
|
+void VideoStream::encode(AVFrame* frame)
|
|
|
|
+{
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ ret = avcodec_send_frame(codecContext, frame);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ throw VideoExportException{ "error encoding frame" };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ while (ret >= 0) {
|
|
|
|
+ ret = avcodec_receive_packet(codecContext, pkt);
|
|
|
|
+ //ret = avcodec_encode_video2(codecContext, pkt, picture, &gotPacket);
|
|
|
|
+ if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
|
|
|
+ return;
|
|
|
|
+ else if (ret < 0) {
|
|
|
|
+ throw VideoExportException{ "error during encoding" };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //printf("encoded frame %3d\"PRId64\" (size=%5d)\n", pkt->pts, pkt->size);
|
|
|
|
+ //fwrite(pkt->data, 1, pkt->size, outfile);
|
|
|
|
+ //av_interleaved_write_frame(formatContext, pkt);
|
|
|
|
+
|
|
|
|
+ av_packet_rescale_ts(pkt, AVRational{ 1, 60 }, stream->time_base);
|
|
|
|
+ pkt->stream_index = stream->index;
|
|
|
|
+
|
|
|
|
+ av_write_frame(formatContext, pkt);
|
|
|
|
+ av_packet_unref(pkt);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|