#include "VideoStream.h" #ifdef FFMPEG_ENABLED #include #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) #define av_frame_alloc avcodec_alloc_frame #define av_frame_free avcodec_free_frame #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) : width{ width & (~1) }, height{ height & (~1) } { // only needed with ffmpeg version < 4 //avcodec_register_all(); codec = avcodec_find_encoder(AV_CODEC_ID_H264); if (!codec) { fprintf(stderr, "invalid codec\n"); exit(1); } AVOutputFormat* oformat = av_guess_format(nullptr, filename.c_str(), nullptr); if (!oformat) oformat = av_guess_format("mp4", nullptr, nullptr); if (oformat == nullptr) throw "invalid format"; codecContext = avcodec_alloc_context3(codec); pkt = av_packet_alloc(); if (!pkt) exit(1); codecContext->bit_rate = bitrate * 1000; codecContext->width = width; codecContext->height = height; codecContext->time_base = AVRational{ 1, fps }; codecContext->framerate = AVRational{ fps, 1 }; codecContext->gop_size = 5; /* emit one intra frame every five frames */ codecContext->max_b_frames = 1; codecContext->pix_fmt = AV_PIX_FMT_YUV420P; formatContext = avformat_alloc_context(); formatContext->oformat = oformat; formatContext->video_codec_id = oformat->video_codec; stream = avformat_new_stream(formatContext, codec); if (!stream) throw "error"; params = avcodec_parameters_alloc(); avcodec_parameters_from_context(params, codecContext); stream->codecpar = params; /*AVCPBProperties *props; props = (AVCPBProperties*) av_stream_new_side_data( stream, AV_PKT_DATA_CPB_PROPERTIES, sizeof(*props)); props->buffer_size = 1024 * 1024; props->max_bitrate = 0; props->min_bitrate = 0; props->avg_bitrate = 0; props->vbv_delay = UINT64_MAX;*/ if (codec->id == AV_CODEC_ID_H264) av_opt_set(codecContext->priv_data, "preset", preset, 0); if (avcodec_open2(codecContext, codec, nullptr) < 0) { fprintf(stderr, "could not open codec\n"); exit(1); } avio_open(&formatContext->pb, filename.c_str(), AVIO_FLAG_WRITE); if (avformat_write_header(formatContext, NULL) < 0) { throw "error"; } /*file = fopen(filename.c_str(), "wb"); if (!file) { fprintf(stderr, "could not open %s\n", filename.c_str()); exit(1); }*/ picture = av_frame_alloc(); av_frame_make_writable(picture); picture->format = codecContext->pix_fmt; picture->width = codecContext->width; picture->height = codecContext->height; int retval = av_frame_get_buffer(picture, 0); if (retval < 0) { fprintf(stderr, "could not alloc the frame data\n"); exit(1); } //av_image_alloc(picture->data, picture->linesize, width, height, codecContext->pix_fmt, 32); swsContext = sws_getContext(width, height, AV_PIX_FMT_RGB24, width, height, 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 %3ld\"PRId64\" (size=%5d)\n", long(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); } } VideoStream::~VideoStream() { /* flush the encoder */ encode(nullptr); 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); avio_close(formatContext->pb); av_frame_unref(picture); //av_free(codecContext); avcodec_parameters_free(¶ms); avcodec_free_context(&codecContext); av_frame_free(&picture); 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); } }*/ } void VideoStream::addFrame(const Bitmap& frame) { int retval = av_frame_make_writable(picture); if (retval < 0) exit(1); /* 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;ydata[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 gammaCorrected = frame.map(gammaCorrect);*/ const uint8_t* pixelPointer[] = { reinterpret_cast(frame.pixels.get()), 0 }; const int linesizeIn[] = { int(frame.width * sizeof(RGBColor)) }; sws_scale(swsContext, pixelPointer, linesizeIn, 0, frame.height, picture->data, picture->linesize); picture->pts = frameIndex++; /* encode the image */ encode(picture); } #endif // FFMPEG_ENABLED