LCOV - code coverage report
Current view: top level - muse/framework/media/internal/ffmpeg/v8 - videoencoder.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 469 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 MuseScore Limited
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "videoencoder.h"
      23              : #include "ffmpeglibhandler.h"
      24              : 
      25              : #include <algorithm>
      26              : 
      27              : #include "io/file.h"
      28              : #include "io/path.h"
      29              : #include "defer.h"
      30              : #include "log.h"
      31              : 
      32              : namespace muse::media::ffmpeg::v8 {
      33              : struct MemoryReadContext {
      34              :     const uint8_t* data = nullptr;
      35              :     int64_t size = 0;
      36              :     int64_t pos = 0;
      37              : };
      38              : 
      39            0 : static int memReadPacket(void* opaque, uint8_t* buf, int buf_size)
      40              : {
      41            0 :     auto* ctx = static_cast<MemoryReadContext*>(opaque);
      42            0 :     int64_t remaining = ctx->size - ctx->pos;
      43            0 :     int toRead = static_cast<int>(std::min(static_cast<int64_t>(buf_size), remaining));
      44            0 :     if (toRead <= 0) {
      45            0 :         return AVERROR_EOF;
      46              :     }
      47            0 :     memcpy(buf, ctx->data + ctx->pos, toRead);
      48            0 :     ctx->pos += toRead;
      49            0 :     return toRead;
      50              : }
      51              : 
      52            0 : static int64_t memSeek(void* opaque, int64_t offset, int whence)
      53              : {
      54            0 :     auto* ctx = static_cast<MemoryReadContext*>(opaque);
      55            0 :     switch (whence) {
      56            0 :     case SEEK_SET: ctx->pos = offset;
      57            0 :         break;
      58            0 :     case SEEK_CUR: ctx->pos += offset;
      59            0 :         break;
      60            0 :     case SEEK_END: ctx->pos = ctx->size + offset;
      61            0 :         break;
      62            0 :     case AVSEEK_SIZE: return ctx->size;
      63            0 :     default: return -1;
      64              :     }
      65            0 :     if (ctx->pos < 0) {
      66            0 :         ctx->pos = 0;
      67              :     }
      68            0 :     if (ctx->pos > ctx->size) {
      69            0 :         ctx->pos = ctx->size;
      70              :     }
      71            0 :     return ctx->pos;
      72              : }
      73              : 
      74              : struct FFmpeg {
      75              :     int width = 0;
      76              :     int height = 0;
      77              :     unsigned int ptsCounter = 0;
      78              : 
      79              :     // FFmpeg stuff
      80              :     AVFormatContext* formatCtx = nullptr;
      81              :     AVStream* videoStream = nullptr;
      82              :     AVCodecContext* codecCtx = nullptr;
      83              :     const AVCodec* codec = nullptr;
      84              :     // Frame data
      85              :     AVFrame* ppicture = nullptr;
      86              :     uint8_t* picture_buf = nullptr;
      87              :     // Conversion
      88              :     SwsContext* img_convert_ctx = nullptr;
      89              :     // Packet
      90              :     AVPacket* pkt;
      91              : 
      92              :     bool opened = false;
      93              :     bool finished = false;
      94              : };
      95              : 
      96            0 : VideoEncoder::VideoEncoder()
      97              : {
      98            0 :     m_ffmpeg = new FFmpeg();
      99            0 : }
     100              : 
     101            0 : VideoEncoder::~VideoEncoder()
     102              : {
     103            0 :     delete m_ffmpeg;
     104            0 : }
     105              : 
     106            0 : bool VideoEncoder::load(const FFmpegLibPaths& paths)
     107              : {
     108            0 :     auto handler = std::make_shared<FFmpegLibHandler>();
     109            0 :     if (!handler->loadLib(paths.avUtilPath, paths.avCodecPath, paths.avFormatPath,
     110            0 :                           paths.swScalePath, paths.swResamplePath)
     111            0 :         || !handler->loadApi()) {
     112            0 :         LOGW() << "FFmpeg libraries not found";
     113            0 :         return false;
     114              :     }
     115            0 :     m_ffmpegHandler = std::move(handler);
     116            0 :     return true;
     117            0 : }
     118              : 
     119            0 : bool VideoEncoder::open(const muse::io::path_t& fileName, const Options& options)
     120              : {
     121            0 :     m_ffmpeg->ptsCounter = 0;
     122              : 
     123            0 :     m_ffmpeg->width = options.width;
     124            0 :     m_ffmpeg->height = options.height;
     125              : 
     126            0 :     if (m_ffmpegHandler->avformat_alloc_output_context2(&m_ffmpeg->formatCtx, nullptr, options.format.c_str(), fileName.c_str()) < 0
     127            0 :         || !m_ffmpeg->formatCtx) {
     128            0 :         LOGE() << "failed to allocate output context";
     129            0 :         return false;
     130              :     }
     131              : 
     132              :     // Add the video stream
     133            0 :     m_ffmpeg->videoStream = m_ffmpegHandler->avformat_new_stream(m_ffmpeg->formatCtx, nullptr);
     134            0 :     if (!m_ffmpeg->videoStream) {
     135            0 :         LOGE() << "failed allocate stream";
     136            0 :         return false;
     137              :     }
     138              : 
     139            0 :     m_ffmpeg->videoStream->time_base.den = options.fps;
     140            0 :     m_ffmpeg->videoStream->time_base.num = 1;
     141              : 
     142              :     // find the video encoder
     143            0 :     m_ffmpeg->codec = m_ffmpegHandler->avcodec_find_encoder(AV_CODEC_ID_H264);
     144            0 :     if (!m_ffmpeg->codec) {
     145            0 :         LOGE() << "not found codec";
     146            0 :         return false;
     147              :     }
     148            0 :     m_ffmpeg->codecCtx = m_ffmpegHandler->avcodec_alloc_context3(m_ffmpeg->codec);
     149            0 :     if (m_ffmpeg->codecCtx == nullptr) {
     150            0 :         LOGE() << "failed to allocate AV context";
     151            0 :         return false;
     152              :     }
     153              : 
     154            0 :     m_ffmpeg->videoStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
     155            0 :     m_ffmpeg->videoStream->codecpar->codec_id = AV_CODEC_ID_H264;
     156            0 :     m_ffmpeg->videoStream->codecpar->width = options.width;
     157            0 :     m_ffmpeg->videoStream->codecpar->height = options.height;
     158            0 :     m_ffmpeg->videoStream->codecpar->format = AV_PIX_FMT_YUV420P;
     159            0 :     m_ffmpeg->videoStream->codecpar->bit_rate = options.bitrate;
     160              : 
     161            0 :     if (m_ffmpegHandler->avcodec_parameters_to_context(m_ffmpeg->codecCtx, m_ffmpeg->videoStream->codecpar) < 0) {
     162            0 :         m_ffmpegHandler->avcodec_free_context(&m_ffmpeg->codecCtx);
     163            0 :         LOGE() << "failed to set codec parameters from stream";
     164            0 :         return false;
     165              :     }
     166              : 
     167            0 :     m_ffmpeg->codecCtx->profile = AV_PROFILE_H264_MAIN;
     168            0 :     m_ffmpeg->codecCtx->time_base.den = options.fps;
     169            0 :     m_ffmpeg->codecCtx->time_base.num = 1;
     170            0 :     m_ffmpeg->codecCtx->gop_size = options.gop;
     171            0 :     m_ffmpeg->codecCtx->thread_count = 10;
     172            0 :     m_ffmpeg->codecCtx->max_b_frames = 0;
     173            0 :     m_ffmpegHandler->av_opt_set_int(m_ffmpeg->codecCtx, "b_strategy", 1, 0);
     174              : 
     175            0 :     m_ffmpeg->codecCtx->me_cmp = 1;
     176            0 :     m_ffmpeg->codecCtx->me_range = 16;
     177            0 :     m_ffmpegHandler->av_opt_set_int(m_ffmpeg->codecCtx, "hex", 1, 0);
     178              : 
     179            0 :     m_ffmpeg->codecCtx->qmin = 10;
     180            0 :     m_ffmpeg->codecCtx->qmax = 51;
     181            0 :     m_ffmpegHandler->av_opt_set_int(m_ffmpeg->codecCtx, "sc_threshold", 40, 0);
     182            0 :     m_ffmpeg->codecCtx->flags |= AV_CODEC_FLAG_LOOP_FILTER;
     183            0 :     m_ffmpeg->codecCtx->me_subpel_quality = 5;
     184            0 :     m_ffmpeg->codecCtx->i_quant_factor = 0.71f;
     185            0 :     m_ffmpeg->codecCtx->qcompress = 0.6f;
     186            0 :     m_ffmpeg->codecCtx->max_qdiff = 4;
     187              : 
     188            0 :     if (m_ffmpeg->formatCtx->oformat->flags & AVFMT_GLOBALHEADER) {
     189            0 :         m_ffmpeg->codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
     190              :     }
     191              : 
     192            0 :     if (m_ffmpegHandler->avcodec_open2(m_ffmpeg->codecCtx, m_ffmpeg->codec, nullptr) < 0) {
     193            0 :         LOGE() << "failed open codec";
     194            0 :         return false;
     195              :     }
     196              : 
     197              :     // Copy codec params to stream AFTER open - codec adds extradata (SPS/PPS) during open
     198            0 :     if (m_ffmpegHandler->avcodec_parameters_from_context(m_ffmpeg->videoStream->codecpar, m_ffmpeg->codecCtx) < 0) {
     199            0 :         m_ffmpegHandler->avcodec_free_context(&m_ffmpeg->codecCtx);
     200            0 :         LOGE() << "failed to copy codec parameters to stream";
     201            0 :         return false;
     202              :     }
     203              : 
     204              :     // Allocate the YUV frame
     205            0 :     m_ffmpeg->ppicture = m_ffmpegHandler->av_frame_alloc();
     206            0 :     if (!m_ffmpeg->ppicture) {
     207            0 :         LOGE() << "failed allocate frame";
     208            0 :         return false;
     209              :     }
     210              : 
     211            0 :     m_ffmpeg->ppicture->width = options.width;
     212            0 :     m_ffmpeg->ppicture->height = options.height;
     213            0 :     m_ffmpeg->ppicture->format = AV_PIX_FMT_YUV420P;
     214              : 
     215            0 :     int size = m_ffmpegHandler->av_image_get_buffer_size(AV_PIX_FMT_YUV420P, m_ffmpeg->width, m_ffmpeg->height, 1);
     216            0 :     m_ffmpeg->picture_buf = new uint8_t[size];
     217            0 :     if (!m_ffmpeg->picture_buf) {
     218            0 :         LOGE() << "failed allocate frame buf";
     219            0 :         m_ffmpegHandler->av_frame_free(&m_ffmpeg->ppicture);
     220            0 :         return false;
     221              :     }
     222              : 
     223              :     // Setup the planes
     224            0 :     m_ffmpegHandler->av_image_fill_arrays(m_ffmpeg->ppicture->data, m_ffmpeg->ppicture->linesize, m_ffmpeg->picture_buf,
     225            0 :                                           AV_PIX_FMT_YUV420P, m_ffmpeg->width, m_ffmpeg->height, 1);
     226              : 
     227            0 :     if (m_ffmpegHandler->avio_open(&m_ffmpeg->formatCtx->pb, fileName.c_str(), AVIO_FLAG_WRITE) < 0) {
     228            0 :         LOGE() << "failed open file: " << fileName;
     229            0 :         return false;
     230              :     }
     231              : 
     232            0 :     int ret = m_ffmpegHandler->avformat_write_header(m_ffmpeg->formatCtx, NULL);
     233            0 :     if (ret < 0) {
     234            0 :         LOGE() << "failed write AV header";
     235            0 :         return false;
     236              :     }
     237              : 
     238              :     //av_dump_format(m_ffmpeg->formatCtx, 0, fileName.c_str(), 1);
     239              : 
     240            0 :     m_ffmpeg->opened = true;
     241            0 :     m_outputPath = fileName;
     242              : 
     243            0 :     return true;
     244              : }
     245              : 
     246            0 : void VideoEncoder::finishEncode()
     247              : {
     248            0 :     if (!m_ffmpeg->opened || m_ffmpeg->finished) {
     249            0 :         return;
     250              :     }
     251              : 
     252            0 :     int ret = m_ffmpegHandler->avcodec_send_frame(m_ffmpeg->codecCtx, NULL);
     253            0 :     if (ret < 0) {
     254            0 :         LOGE() << "failed to flush encoder buffer";
     255            0 :         return;
     256              :     }
     257              : 
     258              :     while (true) {
     259            0 :         m_ffmpeg->pkt = m_ffmpegHandler->av_packet_alloc();
     260            0 :         ret = m_ffmpegHandler->avcodec_receive_packet(m_ffmpeg->codecCtx, m_ffmpeg->pkt);
     261            0 :         if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
     262            0 :             m_ffmpegHandler->av_packet_free(&m_ffmpeg->pkt);
     263            0 :             break;
     264            0 :         } else if (ret < 0) {
     265            0 :             m_ffmpegHandler->av_packet_free(&m_ffmpeg->pkt);
     266            0 :             LOGE() << "error during encoding";
     267            0 :             return;
     268              :         }
     269              : 
     270            0 :         m_ffmpeg->pkt->pts = m_ffmpegHandler->av_rescale_q(m_ffmpeg->ptsCounter, m_ffmpeg->codecCtx->time_base,
     271            0 :                                                            m_ffmpeg->videoStream->time_base);
     272            0 :         m_ffmpeg->pkt->dts = m_ffmpegHandler->av_rescale_q(m_ffmpeg->ptsCounter, m_ffmpeg->codecCtx->time_base,
     273            0 :                                                            m_ffmpeg->videoStream->time_base);
     274              : 
     275            0 :         m_ffmpeg->ptsCounter++;
     276              : 
     277            0 :         ret = m_ffmpegHandler->av_interleaved_write_frame(m_ffmpeg->formatCtx, m_ffmpeg->pkt);
     278            0 :         if (ret < 0) {
     279            0 :             m_ffmpegHandler->av_packet_free(&m_ffmpeg->pkt);
     280            0 :             return;
     281              :         }
     282            0 :         m_ffmpegHandler->av_packet_free(&m_ffmpeg->pkt);
     283              :     }
     284              : 
     285            0 :     m_ffmpegHandler->av_write_trailer(m_ffmpeg->formatCtx);
     286              : 
     287            0 :     if (m_ffmpeg->formatCtx->pb) {
     288            0 :         m_ffmpegHandler->avio_close(m_ffmpeg->formatCtx->pb);
     289            0 :         m_ffmpeg->formatCtx->pb = nullptr;
     290              :     }
     291              : 
     292            0 :     m_ffmpeg->finished = true;
     293              : }
     294              : 
     295            0 : void VideoEncoder::close()
     296              : {
     297            0 :     if (!m_ffmpeg->opened) {
     298            0 :         return;
     299              :     }
     300              : 
     301            0 :     if (!m_ffmpeg->finished) {
     302            0 :         finishEncode();
     303              :     }
     304              : 
     305            0 :     m_ffmpegHandler->avcodec_free_context(&m_ffmpeg->codecCtx);
     306              : 
     307            0 :     if (m_ffmpeg->picture_buf) {
     308            0 :         delete[] m_ffmpeg->picture_buf;
     309            0 :         m_ffmpeg->picture_buf = 0;
     310              :     }
     311              : 
     312            0 :     if (m_ffmpeg->ppicture) {
     313            0 :         m_ffmpegHandler->av_frame_free(&m_ffmpeg->ppicture);
     314            0 :         m_ffmpeg->ppicture = 0;
     315              :     }
     316              : 
     317            0 :     if (m_ffmpeg->img_convert_ctx) {
     318            0 :         m_ffmpegHandler->sws_freeContext(m_ffmpeg->img_convert_ctx);
     319            0 :         m_ffmpeg->img_convert_ctx = nullptr;
     320              :     }
     321              : 
     322            0 :     if (m_ffmpeg->formatCtx) {
     323            0 :         if (m_ffmpeg->formatCtx->pb) {
     324            0 :             m_ffmpegHandler->avio_close(m_ffmpeg->formatCtx->pb);
     325            0 :             m_ffmpeg->formatCtx->pb = nullptr;
     326              :         }
     327            0 :         m_ffmpegHandler->avformat_free_context(m_ffmpeg->formatCtx);
     328            0 :         m_ffmpeg->formatCtx = nullptr;
     329              :     }
     330            0 :     m_ffmpeg->opened = false;
     331            0 :     m_ffmpeg->finished = false;
     332              : 
     333            0 :     m_outputPath = io::path_t();
     334              : }
     335              : 
     336            0 : bool VideoEncoder::encodeImage(const QImage& img)
     337              : {
     338            0 :     if (!m_ffmpeg->opened) {
     339            0 :         return false;
     340              :     }
     341              : 
     342            0 :     convertImage_sws(img);
     343              : 
     344            0 :     m_ffmpeg->ppicture->pts = m_ffmpegHandler->av_rescale_q(m_ffmpeg->codecCtx->frame_num, m_ffmpeg->codecCtx->time_base,
     345            0 :                                                             m_ffmpeg->videoStream->time_base);
     346              : 
     347            0 :     int ret = m_ffmpegHandler->avcodec_send_frame(m_ffmpeg->codecCtx, m_ffmpeg->ppicture);
     348            0 :     if (ret < 0) {
     349            0 :         return false;
     350              :     }
     351              : 
     352            0 :     while (ret >= 0) {
     353            0 :         m_ffmpeg->pkt = m_ffmpegHandler->av_packet_alloc();
     354            0 :         ret = m_ffmpegHandler->avcodec_receive_packet(m_ffmpeg->codecCtx, m_ffmpeg->pkt);
     355            0 :         if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
     356            0 :             m_ffmpegHandler->av_packet_free(&m_ffmpeg->pkt);
     357            0 :             return true;
     358            0 :         } else if (ret < 0) {
     359            0 :             m_ffmpegHandler->av_packet_free(&m_ffmpeg->pkt);
     360            0 :             LOGE() << "error during encoding";
     361            0 :             return false;
     362              :         }
     363              : 
     364            0 :         m_ffmpeg->pkt->pts = m_ffmpegHandler->av_rescale_q(m_ffmpeg->ptsCounter, m_ffmpeg->codecCtx->time_base,
     365            0 :                                                            m_ffmpeg->videoStream->time_base);
     366            0 :         m_ffmpeg->pkt->dts = m_ffmpegHandler->av_rescale_q(m_ffmpeg->ptsCounter, m_ffmpeg->codecCtx->time_base,
     367            0 :                                                            m_ffmpeg->videoStream->time_base);
     368              : 
     369            0 :         m_ffmpeg->ptsCounter++;
     370              : 
     371            0 :         ret = m_ffmpegHandler->av_interleaved_write_frame(m_ffmpeg->formatCtx, m_ffmpeg->pkt);
     372            0 :         if (ret < 0) {
     373            0 :             m_ffmpegHandler->av_packet_free(&m_ffmpeg->pkt);
     374            0 :             return false;
     375              :         }
     376            0 :         m_ffmpegHandler->av_packet_free(&m_ffmpeg->pkt);
     377              :     }
     378              : 
     379            0 :     return true;
     380              : }
     381              : 
     382            0 : bool VideoEncoder::encodeVideo(const ByteArray& videoData, int maxFrames)
     383              : {
     384            0 :     if (!m_ffmpeg->opened) {
     385            0 :         LOGE() << "encodeVideo: encoder not opened";
     386            0 :         return false;
     387              :     }
     388              : 
     389            0 :     MemoryReadContext memCtx;
     390            0 :     memCtx.data = videoData.constData();
     391            0 :     memCtx.size = static_cast<int64_t>(videoData.size());
     392            0 :     memCtx.pos = 0;
     393              : 
     394              :     static constexpr int AVIO_BUF_SIZE = 4096;
     395            0 :     unsigned char* avioBuf = static_cast<unsigned char*>(m_ffmpegHandler->av_malloc(AVIO_BUF_SIZE));
     396            0 :     if (!avioBuf) {
     397            0 :         LOGE() << "encodeVideo: failed to allocate avio buffer";
     398            0 :         return false;
     399              :     }
     400              : 
     401            0 :     AVIOContext* avioCtx = m_ffmpegHandler->avio_alloc_context(
     402            0 :         avioBuf, AVIO_BUF_SIZE, 0, &memCtx, memReadPacket, nullptr, memSeek);
     403            0 :     if (!avioCtx) {
     404            0 :         LOGE() << "encodeVideo: failed to allocate AVIOContext";
     405            0 :         return false;
     406              :     }
     407              : 
     408            0 :     AVFormatContext* inputFmtCtx = m_ffmpegHandler->avformat_alloc_context();
     409            0 :     if (!inputFmtCtx) {
     410            0 :         m_ffmpegHandler->avio_context_free(&avioCtx);
     411            0 :         LOGE() << "encodeVideo: failed to allocate format context";
     412            0 :         return false;
     413              :     }
     414            0 :     inputFmtCtx->pb = avioCtx;
     415              : 
     416            0 :     AVCodecContext* decodeCtx = nullptr;
     417            0 :     AVFrame* decodedFrame = nullptr;
     418            0 :     AVPacket* packet = nullptr;
     419              : 
     420            0 :     SwsContext* decSwsCtx = nullptr;
     421              : 
     422            0 :     DEFER {
     423            0 :         if (decSwsCtx) {
     424            0 :             m_ffmpegHandler->sws_freeContext(decSwsCtx);
     425              :         }
     426            0 :         if (inputFmtCtx) {
     427            0 :             m_ffmpegHandler->avformat_close_input(&inputFmtCtx);
     428              :         }
     429            0 :         if (avioCtx) {
     430            0 :             m_ffmpegHandler->avio_context_free(&avioCtx);
     431              :         }
     432            0 :         if (decodeCtx) {
     433            0 :             m_ffmpegHandler->avcodec_free_context(&decodeCtx);
     434              :         }
     435            0 :         if (decodedFrame) {
     436            0 :             m_ffmpegHandler->av_frame_free(&decodedFrame);
     437              :         }
     438            0 :         if (packet) {
     439            0 :             m_ffmpegHandler->av_packet_free(&packet);
     440              :         }
     441            0 :     };
     442              : 
     443            0 :     if (m_ffmpegHandler->avformat_open_input(&inputFmtCtx, nullptr, nullptr, nullptr) < 0) {
     444            0 :         LOGE() << "encodeVideo: failed to open input from memory";
     445            0 :         return false;
     446              :     }
     447              : 
     448            0 :     if (m_ffmpegHandler->avformat_find_stream_info(inputFmtCtx, nullptr) < 0) {
     449            0 :         LOGE() << "encodeVideo: failed to find stream info";
     450            0 :         return false;
     451              :     }
     452              : 
     453            0 :     int videoStreamIdx = -1;
     454            0 :     for (unsigned i = 0; i < inputFmtCtx->nb_streams; i++) {
     455            0 :         if (inputFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
     456            0 :             videoStreamIdx = static_cast<int>(i);
     457            0 :             break;
     458              :         }
     459              :     }
     460              : 
     461            0 :     if (videoStreamIdx < 0) {
     462            0 :         LOGE() << "encodeVideo: no video stream found";
     463            0 :         return false;
     464              :     }
     465              : 
     466            0 :     AVCodecParameters* codecPar = inputFmtCtx->streams[videoStreamIdx]->codecpar;
     467              : 
     468            0 :     const AVCodec* decoder = m_ffmpegHandler->avcodec_find_decoder(codecPar->codec_id);
     469            0 :     if (!decoder) {
     470            0 :         LOGE() << "encodeVideo: decoder not found for codec_id " << codecPar->codec_id;
     471            0 :         return false;
     472              :     }
     473              : 
     474            0 :     decodeCtx = m_ffmpegHandler->avcodec_alloc_context3(decoder);
     475            0 :     if (!decodeCtx) {
     476            0 :         LOGE() << "encodeVideo: failed to allocate decoder context";
     477            0 :         return false;
     478              :     }
     479              : 
     480            0 :     m_ffmpegHandler->avcodec_parameters_to_context(decodeCtx, codecPar);
     481              : 
     482            0 :     if (m_ffmpegHandler->avcodec_open2(decodeCtx, decoder, nullptr) < 0) {
     483            0 :         LOGE() << "encodeVideo: failed to open decoder";
     484            0 :         return false;
     485              :     }
     486              : 
     487            0 :     decSwsCtx = m_ffmpegHandler->sws_getCachedContext(
     488              :         nullptr,
     489              :         decodeCtx->width, decodeCtx->height, decodeCtx->pix_fmt,
     490            0 :         m_ffmpeg->width, m_ffmpeg->height, AV_PIX_FMT_YUV420P,
     491              :         SWS_BICUBIC, nullptr, nullptr, nullptr);
     492              : 
     493            0 :     if (!decSwsCtx) {
     494            0 :         LOGE() << "encodeVideo: failed to create sws context";
     495            0 :         return false;
     496              :     }
     497              : 
     498            0 :     decodedFrame = m_ffmpegHandler->av_frame_alloc();
     499            0 :     packet = m_ffmpegHandler->av_packet_alloc();
     500              : 
     501            0 :     int framesEncoded = 0;
     502            0 :     bool done = false;
     503              : 
     504            0 :     auto encodeDecodedFrame = [&]() -> bool {
     505            0 :         m_ffmpegHandler->sws_scale(decSwsCtx,
     506            0 :                                    decodedFrame->data, decodedFrame->linesize,
     507            0 :                                    0, decodedFrame->height,
     508            0 :                                    m_ffmpeg->ppicture->data, m_ffmpeg->ppicture->linesize);
     509              : 
     510            0 :         m_ffmpeg->ppicture->pts = m_ffmpegHandler->av_rescale_q(
     511            0 :             m_ffmpeg->codecCtx->frame_num, m_ffmpeg->codecCtx->time_base, m_ffmpeg->videoStream->time_base);
     512              : 
     513            0 :         int ret = m_ffmpegHandler->avcodec_send_frame(m_ffmpeg->codecCtx, m_ffmpeg->ppicture);
     514            0 :         if (ret < 0) {
     515            0 :             return false;
     516              :         }
     517              : 
     518            0 :         while (ret >= 0) {
     519            0 :             AVPacket* outPkt = m_ffmpegHandler->av_packet_alloc();
     520            0 :             ret = m_ffmpegHandler->avcodec_receive_packet(m_ffmpeg->codecCtx, outPkt);
     521            0 :             if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
     522            0 :                 m_ffmpegHandler->av_packet_free(&outPkt);
     523            0 :                 break;
     524              :             }
     525            0 :             if (ret < 0) {
     526            0 :                 m_ffmpegHandler->av_packet_free(&outPkt);
     527            0 :                 return false;
     528              :             }
     529              : 
     530            0 :             outPkt->pts = m_ffmpegHandler->av_rescale_q(m_ffmpeg->ptsCounter, m_ffmpeg->codecCtx->time_base,
     531            0 :                                                         m_ffmpeg->videoStream->time_base);
     532            0 :             outPkt->dts = m_ffmpegHandler->av_rescale_q(m_ffmpeg->ptsCounter, m_ffmpeg->codecCtx->time_base,
     533            0 :                                                         m_ffmpeg->videoStream->time_base);
     534            0 :             m_ffmpeg->ptsCounter++;
     535              : 
     536            0 :             m_ffmpegHandler->av_interleaved_write_frame(m_ffmpeg->formatCtx, outPkt);
     537            0 :             m_ffmpegHandler->av_packet_free(&outPkt);
     538              :         }
     539              : 
     540            0 :         framesEncoded++;
     541            0 :         if (maxFrames > 0 && framesEncoded >= maxFrames) {
     542            0 :             done = true;
     543              :         }
     544              : 
     545            0 :         return true;
     546            0 :     };
     547              : 
     548            0 :     while (!done && m_ffmpegHandler->av_read_frame(inputFmtCtx, packet) >= 0) {
     549            0 :         if (packet->stream_index != videoStreamIdx) {
     550            0 :             m_ffmpegHandler->av_packet_unref(packet);
     551            0 :             continue;
     552              :         }
     553              : 
     554            0 :         int ret = m_ffmpegHandler->avcodec_send_packet(decodeCtx, packet);
     555            0 :         m_ffmpegHandler->av_packet_unref(packet);
     556              : 
     557            0 :         while (!done && ret >= 0) {
     558            0 :             ret = m_ffmpegHandler->avcodec_receive_frame(decodeCtx, decodedFrame);
     559            0 :             if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
     560              :                 break;
     561              :             }
     562            0 :             if (ret < 0) {
     563            0 :                 LOGE() << "encodeVideo: error receiving decoded frame";
     564            0 :                 return false;
     565              :             }
     566              : 
     567            0 :             if (!encodeDecodedFrame()) {
     568            0 :                 LOGE() << "encodeVideo: error encoding frame";
     569            0 :                 return false;
     570              :             }
     571              :         }
     572              :     }
     573              : 
     574            0 :     if (!done) {
     575            0 :         m_ffmpegHandler->avcodec_send_packet(decodeCtx, nullptr);
     576              :         while (true) {
     577            0 :             int ret = m_ffmpegHandler->avcodec_receive_frame(decodeCtx, decodedFrame);
     578            0 :             if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
     579              :                 break;
     580              :             }
     581            0 :             if (ret < 0 || !encodeDecodedFrame() || done) {
     582            0 :                 break;
     583              :             }
     584            0 :         }
     585              :     }
     586              : 
     587            0 :     LOGI() << "encodeVideo: encoded " << framesEncoded << " frames from memory buffer";
     588            0 :     return true;
     589            0 : }
     590              : 
     591            0 : bool VideoEncoder::addAudio(const io::path_t& audioPath)
     592              : {
     593            0 :     if (m_outputPath.empty()) {
     594            0 :         LOGE() << "addAudio: encoder was not opened or path not set";
     595            0 :         return false;
     596              :     }
     597              : 
     598            0 :     const io::path_t videoPath = m_outputPath;
     599            0 :     const io::path_t tmpPath = m_outputPath + ".tmp";
     600              : 
     601            0 :     AVFormatContext* videoFmtCtx = nullptr;
     602            0 :     AVFormatContext* audioFmtCtx = nullptr;
     603            0 :     AVFormatContext* outputFmtCtx = nullptr;
     604              : 
     605            0 :     bool cleaned = false;
     606            0 :     auto cleanup = [&]() {
     607            0 :         if (cleaned) {
     608            0 :             return;
     609              :         }
     610              : 
     611            0 :         if (videoFmtCtx) {
     612            0 :             m_ffmpegHandler->avformat_close_input(&videoFmtCtx);
     613              :         }
     614            0 :         if (audioFmtCtx) {
     615            0 :             m_ffmpegHandler->avformat_close_input(&audioFmtCtx);
     616              :         }
     617            0 :         if (outputFmtCtx) {
     618            0 :             if (outputFmtCtx->pb) {
     619            0 :                 m_ffmpegHandler->avio_close(outputFmtCtx->pb);
     620            0 :                 outputFmtCtx->pb = nullptr;
     621              :             }
     622            0 :             m_ffmpegHandler->avformat_free_context(outputFmtCtx);
     623              :         }
     624              : 
     625            0 :         cleaned = true;
     626            0 :     };
     627              : 
     628            0 :     DEFER {
     629            0 :         cleanup();
     630            0 :     };
     631              : 
     632            0 :     if (m_ffmpegHandler->avformat_open_input(&videoFmtCtx, videoPath.c_str(), nullptr, nullptr) < 0) {
     633            0 :         LOGE() << "addAudio: failed to open video input: " << videoPath;
     634            0 :         return false;
     635              :     }
     636            0 :     if (m_ffmpegHandler->avformat_find_stream_info(videoFmtCtx, nullptr) < 0) {
     637            0 :         LOGE() << "addAudio: failed to find video stream info";
     638            0 :         return false;
     639              :     }
     640              : 
     641            0 :     if (m_ffmpegHandler->avformat_open_input(&audioFmtCtx, audioPath.c_str(), nullptr, nullptr) < 0) {
     642            0 :         LOGE() << "addAudio: failed to open audio input: " << audioPath;
     643            0 :         return false;
     644              :     }
     645            0 :     if (m_ffmpegHandler->avformat_find_stream_info(audioFmtCtx, nullptr) < 0) {
     646            0 :         LOGE() << "addAudio: failed to find audio stream info";
     647            0 :         return false;
     648              :     }
     649              : 
     650            0 :     if (m_ffmpegHandler->avformat_alloc_output_context2(&outputFmtCtx, nullptr, "mp4", tmpPath.c_str()) < 0 || !outputFmtCtx) {
     651            0 :         LOGE() << "addAudio: failed to allocate output context";
     652            0 :         return false;
     653              :     }
     654              : 
     655            0 :     int videoInIdx = -1;
     656            0 :     int audioInIdx = -1;
     657            0 :     int outVideoIdx = -1;
     658            0 :     int outAudioIdx = -1;
     659              : 
     660            0 :     for (unsigned i = 0; i < videoFmtCtx->nb_streams; i++) {
     661            0 :         if (videoFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
     662            0 :             videoInIdx = static_cast<int>(i);
     663            0 :             AVStream* outStream = m_ffmpegHandler->avformat_new_stream(outputFmtCtx, nullptr);
     664            0 :             if (!outStream) {
     665            0 :                 LOGE() << "addAudio: failed to create output video stream";
     666            0 :                 return false;
     667              :             }
     668            0 :             m_ffmpegHandler->avcodec_parameters_copy(outStream->codecpar, videoFmtCtx->streams[i]->codecpar);
     669            0 :             outStream->codecpar->codec_tag = 0;
     670            0 :             outStream->time_base = videoFmtCtx->streams[i]->time_base;
     671            0 :             outVideoIdx = outStream->index;
     672            0 :             break;
     673              :         }
     674              :     }
     675              : 
     676            0 :     for (unsigned i = 0; i < audioFmtCtx->nb_streams; i++) {
     677            0 :         if (audioFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
     678            0 :             audioInIdx = static_cast<int>(i);
     679            0 :             AVStream* outStream = m_ffmpegHandler->avformat_new_stream(outputFmtCtx, nullptr);
     680            0 :             if (!outStream) {
     681            0 :                 LOGE() << "addAudio: failed to create output audio stream";
     682            0 :                 return false;
     683              :             }
     684            0 :             m_ffmpegHandler->avcodec_parameters_copy(outStream->codecpar, audioFmtCtx->streams[i]->codecpar);
     685            0 :             outStream->codecpar->codec_tag = 0;
     686            0 :             outStream->time_base = audioFmtCtx->streams[i]->time_base;
     687            0 :             outAudioIdx = outStream->index;
     688            0 :             break;
     689              :         }
     690              :     }
     691              : 
     692            0 :     if (videoInIdx < 0 || audioInIdx < 0) {
     693            0 :         LOGE() << "addAudio: could not find video or audio stream";
     694            0 :         return false;
     695              :     }
     696              : 
     697            0 :     if (m_ffmpegHandler->avio_open(&outputFmtCtx->pb, tmpPath.c_str(), AVIO_FLAG_WRITE) < 0) {
     698            0 :         LOGE() << "addAudio: failed to open output file: " << tmpPath;
     699            0 :         return false;
     700              :     }
     701              : 
     702            0 :     if (m_ffmpegHandler->avformat_write_header(outputFmtCtx, nullptr) < 0) {
     703            0 :         LOGE() << "addAudio: failed to write header";
     704            0 :         return false;
     705              :     }
     706              : 
     707            0 :     AVPacket* pkt = m_ffmpegHandler->av_packet_alloc();
     708              : 
     709            0 :     while (m_ffmpegHandler->av_read_frame(videoFmtCtx, pkt) >= 0) {
     710            0 :         if (pkt->stream_index == videoInIdx) {
     711            0 :             pkt->stream_index = outVideoIdx;
     712            0 :             m_ffmpegHandler->av_packet_rescale_ts(pkt, videoFmtCtx->streams[videoInIdx]->time_base,
     713            0 :                                                   outputFmtCtx->streams[outVideoIdx]->time_base);
     714            0 :             pkt->pos = -1;
     715            0 :             m_ffmpegHandler->av_interleaved_write_frame(outputFmtCtx, pkt);
     716              :         }
     717            0 :         m_ffmpegHandler->av_packet_unref(pkt);
     718              :     }
     719              : 
     720            0 :     while (m_ffmpegHandler->av_read_frame(audioFmtCtx, pkt) >= 0) {
     721            0 :         if (pkt->stream_index == audioInIdx) {
     722            0 :             pkt->stream_index = outAudioIdx;
     723            0 :             m_ffmpegHandler->av_packet_rescale_ts(pkt, audioFmtCtx->streams[audioInIdx]->time_base,
     724            0 :                                                   outputFmtCtx->streams[outAudioIdx]->time_base);
     725            0 :             pkt->pos = -1;
     726            0 :             m_ffmpegHandler->av_interleaved_write_frame(outputFmtCtx, pkt);
     727              :         }
     728            0 :         m_ffmpegHandler->av_packet_unref(pkt);
     729              :     }
     730              : 
     731            0 :     m_ffmpegHandler->av_packet_free(&pkt);
     732            0 :     m_ffmpegHandler->av_write_trailer(outputFmtCtx);
     733              : 
     734            0 :     cleanup();
     735              : 
     736            0 :     if (!io::File::copy(tmpPath, videoPath, true)) {
     737            0 :         LOGE() << "addAudio: failed to replace with muxed file";
     738            0 :         return false;
     739              :     }
     740              : 
     741            0 :     io::File::remove(tmpPath);
     742              : 
     743            0 :     return true;
     744            0 : }
     745              : 
     746            0 : bool VideoEncoder::convertImage_sws(const QImage& img)
     747              : {
     748              :     // Check if the image matches the size
     749            0 :     if (img.width() != m_ffmpeg->width || img.height() != m_ffmpeg->height) {
     750            0 :         LOGE() << "wrong image size!";
     751            0 :         return false;
     752              :     }
     753              : 
     754            0 :     if (img.format() != QImage::Format_RGB32 && img.format() != QImage::Format_ARGB32) {
     755            0 :         LOGE() << "wrong image format";
     756            0 :         return false;
     757              :     }
     758              : 
     759            0 :     m_ffmpeg->img_convert_ctx = m_ffmpegHandler->sws_getCachedContext(m_ffmpeg->img_convert_ctx, m_ffmpeg->width, m_ffmpeg->height,
     760              :                                                                       AV_PIX_FMT_BGRA,
     761            0 :                                                                       m_ffmpeg->width, m_ffmpeg->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC,
     762              :                                                                       NULL, NULL, NULL);
     763              : 
     764            0 :     if (!m_ffmpeg->img_convert_ctx) {
     765            0 :         LOGE() << "failed initialize the conversion context";
     766            0 :         return false;
     767              :     }
     768              : 
     769              :     uint8_t* srcplanes[3];
     770            0 :     srcplanes[0] = (uint8_t*)img.bits();
     771            0 :     srcplanes[1] = 0;
     772            0 :     srcplanes[2] = 0;
     773              : 
     774              :     int srcstride[3];
     775            0 :     srcstride[0] = img.bytesPerLine();
     776            0 :     srcstride[1] = 0;
     777            0 :     srcstride[2 ]= 0;
     778              : 
     779            0 :     m_ffmpegHandler->sws_scale(m_ffmpeg->img_convert_ctx, srcplanes, srcstride, 0, m_ffmpeg->height, m_ffmpeg->ppicture->data,
     780            0 :                                m_ffmpeg->ppicture->linesize);
     781              : 
     782            0 :     return true;
     783              : }
     784              : }
        

Generated by: LCOV version 2.0-1