VideoStream.cpp 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. #include "VideoStream.h"
  2. #include <iostream>
  3. using alm::VideoExportException;
  4. VideoExportException::VideoExportException(const std::string& err) :
  5. std::runtime_error{ err }
  6. {
  7. }
  8. #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
  9. #define av_frame_alloc avcodec_alloc_frame
  10. #define av_frame_free avcodec_free_frame
  11. #endif
  12. VideoStream::VideoStream(int width, int height, const std::string& filename, int bitrate, int fps, const char* preset) :
  13. width{ width & (~1) }, height{ height & (~1) }
  14. {
  15. // only needed with ffmpeg version < 4
  16. //avcodec_register_all();
  17. codec = avcodec_find_encoder(AV_CODEC_ID_H264);
  18. if (!codec) {
  19. throw VideoExportException{ "could not find h264 encoder" };
  20. }
  21. AVOutputFormat* oformat = av_guess_format(nullptr, filename.c_str(), nullptr);
  22. if (!oformat)
  23. oformat = av_guess_format("mp4", nullptr, nullptr);
  24. if (oformat == nullptr)
  25. throw VideoExportException{ "invalid format" };
  26. codecContext = avcodec_alloc_context3(codec);
  27. pkt = av_packet_alloc();
  28. if (!pkt)
  29. throw VideoExportException{ "could not allocate packet" };
  30. codecContext->bit_rate = bitrate * 1000;
  31. codecContext->width = width;
  32. codecContext->height = height;
  33. codecContext->time_base = AVRational{ 1, fps };
  34. codecContext->framerate = AVRational{ fps, 1 };
  35. codecContext->gop_size = 16; /* one intra frame every 16 frames */
  36. codecContext->max_b_frames = 3;
  37. codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
  38. formatContext = avformat_alloc_context();
  39. if (!formatContext)
  40. throw VideoExportException{ "error allocating format context" };
  41. formatContext->oformat = oformat;
  42. formatContext->video_codec_id = oformat->video_codec;
  43. stream = avformat_new_stream(formatContext, codec);
  44. if (!stream)
  45. throw VideoExportException{ "error creating stream" };
  46. params = avcodec_parameters_alloc();
  47. avcodec_parameters_from_context(params, codecContext);
  48. stream->codecpar = params;
  49. /*AVCPBProperties *props;
  50. props = (AVCPBProperties*) av_stream_new_side_data(
  51. stream, AV_PKT_DATA_CPB_PROPERTIES, sizeof(*props));
  52. props->buffer_size = 1024 * 1024;
  53. props->max_bitrate = 0;
  54. props->min_bitrate = 0;
  55. props->avg_bitrate = 0;
  56. props->vbv_delay = UINT64_MAX;*/
  57. if (codec->id == AV_CODEC_ID_H264)
  58. av_opt_set(codecContext->priv_data, "preset", preset, 0);
  59. if (avcodec_open2(codecContext, codec, nullptr) < 0) {
  60. throw VideoExportException{ "could not open codec" };
  61. }
  62. int opened = avio_open(&formatContext->pb, filename.c_str(), AVIO_FLAG_WRITE);
  63. if (opened < 0) {
  64. throw VideoExportException{ std::string("could not open file '") + filename + "'" };
  65. }
  66. if (avformat_write_header(formatContext, nullptr) < 0) {
  67. throw VideoExportException{ "error writing header" };
  68. }
  69. picture = av_frame_alloc();
  70. if (!picture)
  71. throw VideoExportException{ "error allocating frame" };
  72. picture->format = codecContext->pix_fmt;
  73. picture->width = codecContext->width;
  74. picture->height = codecContext->height;
  75. int retval = av_frame_get_buffer(picture, 0);
  76. if (retval < 0) {
  77. throw VideoExportException{ "could not allocate frame data" };
  78. }
  79. if (av_frame_make_writable(picture) < 0) {
  80. throw VideoExportException{ "error making frame writeable" };
  81. }
  82. //av_image_alloc(picture->data, picture->linesize, width, height, codecContext->pix_fmt, 32);
  83. swsContext = sws_getContext(width, height,
  84. AV_PIX_FMT_RGB24, width, height,
  85. AV_PIX_FMT_YUV420P, 0, 0, 0, 0);
  86. if (!swsContext)
  87. throw VideoExportException{ "error preparing sws context" };
  88. }
  89. VideoStream::~VideoStream()
  90. {
  91. /* flush the encoder */
  92. encode(nullptr);
  93. av_write_trailer(this->formatContext);
  94. avcodec_close(codecContext);
  95. avio_close(formatContext->pb);
  96. av_frame_unref(picture);
  97. avcodec_parameters_free(&params);
  98. avcodec_free_context(&codecContext);
  99. av_frame_free(&picture);
  100. av_packet_free(&pkt);
  101. }
  102. void VideoStream::addFrame(const Bitmap<RGBColor>& frame)
  103. {
  104. int retval = av_frame_make_writable(picture);
  105. if (retval < 0)
  106. throw VideoExportException{ "could not write to frame data" };
  107. /*Bitmap<RGBColor> gammaCorrected = frame.map<RGBColor>(gammaCorrect);*/
  108. const uint8_t* pixelPointer[] = { reinterpret_cast<const uint8_t*>(frame.pixels.get()), 0 };
  109. const int linesizeIn[] = { int(frame.width * sizeof(RGBColor)) };
  110. sws_scale(swsContext, pixelPointer, linesizeIn, 0,
  111. frame.height, picture->data, picture->linesize);
  112. picture->pts = frameIndex++;
  113. /* encode the image */
  114. encode(picture);
  115. }
  116. void VideoStream::encode(AVFrame* frame)
  117. {
  118. int ret;
  119. ret = avcodec_send_frame(codecContext, frame);
  120. if (ret < 0) {
  121. throw VideoExportException{ "error encoding frame" };
  122. }
  123. while (ret >= 0) {
  124. ret = avcodec_receive_packet(codecContext, pkt);
  125. //ret = avcodec_encode_video2(codecContext, pkt, picture, &gotPacket);
  126. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  127. return;
  128. else if (ret < 0) {
  129. throw VideoExportException{ "error during encoding" };
  130. }
  131. //printf("encoded frame %3d\"PRId64\" (size=%5d)\n", pkt->pts, pkt->size);
  132. //fwrite(pkt->data, 1, pkt->size, outfile);
  133. //av_interleaved_write_frame(formatContext, pkt);
  134. av_packet_rescale_ts(pkt, AVRational{ 1, 60 }, stream->time_base);
  135. pkt->stream_index = stream->index;
  136. av_write_frame(formatContext, pkt);
  137. av_packet_unref(pkt);
  138. }
  139. }