src/transcoding/transcode_int_util.cc
Rama master · réplica de lectura del repositorio original.
/*GRB*
Gerbera - https://gerbera.io/
transcode_int_util.cc - this file is part of Gerbera.
Copyright (C) 2026 Gerbera Contributors
Gerbera is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
Gerbera is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gerbera. If not, see <http://www.gnu.org/licenses/>.
$Id$
*/
// Based in https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/transcode.c
/*
* Copyright (c) 2010 Nicolas George
* Copyright (c) 2011 Stefano Sabatini
* Copyright (c) 2014 Andrey Utkin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/// @file transcoding/transcode_int_util.cc
#define GRB_LOG_FAC GrbLogFacility::transcoding
#ifdef HAVE_FFMPEG
#include "transcode_int_util.h"
#include "config/result/transcoding.h"
#include "util/logger.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavformat/avformat.h>
#include <libavutil/channel_layout.h>
#include <libavutil/mem.h>
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
}
#include "util/ffmpeg_compat.h"
#define FILTERNAME_IN "in"
#define FILTERNAME_OUT "out"
FilteringContext::FilteringContext(
AVCodecContext* dec_ctx,
AVCodecContext* enc_ctx,
std::size_t stream_index,
const std::string& filter_spec,
const std::shared_ptr<TranscodingProfile>& profile)
: decContext(dec_ctx)
, encContext(enc_ctx)
, streamNumber(stream_index)
{
int ret = 0;
// Allocate filter graph components
outputs = avfilter_inout_alloc();
if (!outputs) {
log_error("avfilter_inout_alloc out #{} failed", stream_index);
return;
}
inputs = avfilter_inout_alloc();
if (!inputs) {
log_error("avfilter_inout_alloc in #{} failed", stream_index);
return;
}
filter_graph = avfilter_graph_alloc();
if (!filter_graph) {
log_error("avfilter_graph_alloc #{} failed", stream_index);
return;
}
// Create audio or video buffers
if (decContext->codec_type == AVMEDIA_TYPE_VIDEO) {
if (!createVideoBuffers())
return;
} else if (decContext->codec_type == AVMEDIA_TYPE_AUDIO) {
if (!createAudioBuffers())
return;
} else {
return;
}
// Configure Endpoints for the filter graph.
outputs->name = av_strdup(FILTERNAME_IN);
outputs->filter_ctx = buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = nullptr;
if (!outputs->name) {
log_error("outputs->name #{} failed", stream_index);
return;
}
inputs->name = av_strdup(FILTERNAME_OUT);
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = nullptr;
if (!inputs->name) {
log_error("inputs->name #{} failed", stream_index);
return;
}
// Parse and configure the filter graph
ret = avfilter_graph_parse_ptr(filter_graph, filter_spec.c_str(), &inputs, &outputs, nullptr);
if (ret < 0) {
log_error("avfilter_graph_parse_ptr #{} failed", stream_index);
return;
}
ret = avfilter_graph_config(filter_graph, nullptr);
if (ret < 0) {
log_error("avfilter_graph_config #{} failed", stream_index);
return;
}
if (encContext->frame_size > 0 && buffersink_ctx->inputs && buffersink_ctx->inputs[0])
av_buffersink_set_frame_size(buffersink_ctx, encContext->frame_size);
// Allocate encoder packet and frame
this->encPacket = av_packet_alloc();
if (!this->encPacket) {
log_error("av_packet_alloc #{} failed", stream_index);
return;
}
this->filtered_frame = av_frame_alloc();
if (!this->filtered_frame) {
log_error("av_frame_alloc #{} failed", stream_index);
return;
}
}
FilteringContext::~FilteringContext()
{
log_debug("cleanup");
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
if (filter_graph) {
log_debug("avfilter_graph_free #{}", streamNumber);
// all filters are globally managed
if (filter_graph->filters) {
filter_graph->filters[0] = nullptr;
filter_graph->nb_filters = 0;
}
avfilter_graph_free(&filter_graph);
av_packet_free(&encPacket);
av_frame_free(&filtered_frame);
}
inputs = nullptr;
outputs = nullptr;
filter_graph = nullptr;
encPacket = nullptr;
filtered_frame = nullptr;
}
bool FilteringContext::createVideoBuffers()
{
if (!createVideoBufferSource("buffer")) {
log_error("Failed to create Video buffer source");
return false;
}
if (!createVideoBufferSink("buffersink")) {
log_error("Failed to create Video buffer sink");
return false;
}
return true;
}
bool FilteringContext::createVideoBufferSource(const std::string& name)
{
const AVFilter* buffersrc = avfilter_get_by_name(name.c_str());
if (!buffersrc) {
log_error("filtering source element not found");
return false;
}
// Format: video_size=WxH:pix_fmt=FMT:time_base=NUM/DEN:pixel_aspect=NUM/DEN
auto args = fmt::format("video_size={}x{}:pix_fmt={}:time_base={}/{}:pixel_aspect={}/{}",
decContext->width, decContext->height,
decContext->pix_fmt,
decContext->pkt_timebase.num, decContext->pkt_timebase.den,
decContext->sample_aspect_ratio.num, decContext->sample_aspect_ratio.den);
// Create the source filter in the graph
int ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, FILTERNAME_IN,
args.c_str(), nullptr, filter_graph);
if (ret < 0) {
log_error("Cannot create buffer source (error '{}')", av_err2str(ret));
return false;
}
return true;
}
bool FilteringContext::createVideoBufferSink(const std::string& name)
{
const AVFilter* buffersink = avfilter_get_by_name(name.c_str());
if (!buffersink) {
log_error("filtering sink element not found");
return false;
}
buffersink_ctx = avfilter_graph_alloc_filter(filter_graph, buffersink, FILTERNAME_OUT);
if (!buffersink_ctx) {
log_error("Cannot create buffer sink");
return false;
}
// Set output pixel format
int ret = av_opt_set_bin(buffersink_ctx, "pix_fmts",
(uint8_t*)&encContext->pix_fmt, sizeof(encContext->pix_fmt),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
log_error("Cannot set output pixel format (error '{}')", av_err2str(ret));
}
ret = avfilter_init_dict(buffersink_ctx, nullptr);
if (ret < 0) {
log_error("Cannot initialize buffer sink (error '{}')", av_err2str(ret));
return false;
}
return true;
}
bool FilteringContext::createAudioBuffers()
{
if (!createAudioBufferSource("abuffer")) {
log_error("Failed to create audio buffer source");
return false;
}
if (!createAudioBufferSink("abuffersink")) {
log_error("Failed to create audio buffer sink");
return false;
}
return true;
}
bool FilteringContext::createAudioBufferSource(const std::string& name)
{
const AVFilter* buffersrc = avfilter_get_by_name(name.c_str());
if (!buffersrc) {
log_error("filtering source element not found");
return false;
}
// Handle FFmpeg version differences for channel layout
#if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 0, 0))
if (!decContext->channel_layout)
decContext->channel_layout = av_get_default_channel_layout(decContext->channels);
auto buf = decContext->channel_layout;
#else
char buf[64];
if (decContext->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC)
av_channel_layout_default(&decContext->ch_layout, decContext->ch_layout.nb_channels);
av_channel_layout_describe(&decContext->ch_layout, buf, sizeof(buf));
#endif
// Format: time_base=NUM/DEN:sample_rate=SR:sample_fmt=FMT:channel_layout=LAYOUT
auto args = fmt::format("time_base={}/{}:sample_rate={}:sample_fmt={}:channel_layout={}",
decContext->pkt_timebase.num, decContext->pkt_timebase.den, decContext->sample_rate,
av_get_sample_fmt_name(decContext->sample_fmt),
buf);
int ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, FILTERNAME_IN,
args.c_str(), nullptr, filter_graph);
if (ret < 0) {
log_error("Cannot create audio buffer source (error '{}')", av_err2str(ret));
return false;
}
return true;
}
bool FilteringContext::createAudioBufferSink(const std::string& name)
{
const AVFilter* buffersink = avfilter_get_by_name(name.c_str());
if (!buffersink) {
log_error("filtering sink element not found");
return false;
}
buffersink_ctx = avfilter_graph_alloc_filter(filter_graph, buffersink, FILTERNAME_OUT);
if (!buffersink_ctx) {
log_error("Cannot create audio buffer sink");
return false;
}
#if (LIBAVFILTER_VERSION_INT >= AV_VERSION_INT(10, 6, 100))
// FFmpeg 7.1 and newer: array options
int ret = av_opt_set_array(buffersink_ctx, "sample_formats", AV_OPT_SEARCH_CHILDREN,
0, 1, AV_OPT_TYPE_SAMPLE_FMT, &encContext->sample_fmt);
if (ret < 0) {
log_error("Cannot set output sample format (error '{}')", av_err2str(ret));
return false;
}
ret = av_opt_set_array(buffersink_ctx, "samplerates", AV_OPT_SEARCH_CHILDREN,
0, 1, AV_OPT_TYPE_INT, &encContext->sample_rate);
if (ret < 0) {
log_error("Cannot set output sample rate (error '{}')", av_err2str(ret));
return false;
}
ret = av_opt_set_array(buffersink_ctx, "channel_layouts", AV_OPT_SEARCH_CHILDREN,
0, 1, AV_OPT_TYPE_CHLAYOUT, &encContext->ch_layout);
if (ret < 0) {
log_error("Cannot set output channel layout (error '{}')", av_err2str(ret));
return false;
}
#else
// Set output sample format, channel layout, and sample rate
int ret = av_opt_set_bin(buffersink_ctx, "sample_fmts",
(uint8_t*)&encContext->sample_fmt, sizeof(encContext->sample_fmt),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
log_error("Cannot set output sample format (error '{}')", av_err2str(ret));
return false;
}
#if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 0, 0))
ret = av_opt_set_bin(buffersink_ctx, "channel_layouts",
(uint8_t*)&encContext->channel_layout,
sizeof(encContext->channel_layout), AV_OPT_SEARCH_CHILDREN);
#else
char buf[64];
av_channel_layout_describe(&encContext->ch_layout, buf, sizeof(buf));
ret = av_opt_set(buffersink_ctx, "ch_layouts",
buf, AV_OPT_SEARCH_CHILDREN);
#endif
if (ret < 0) {
log_error("Cannot set output channel layout (error '{}')", av_err2str(ret));
return false;
}
ret = av_opt_set_bin(buffersink_ctx, "sample_rates",
(uint8_t*)&encContext->sample_rate, sizeof(encContext->sample_rate),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
log_error("Cannot set output sample rate (error '{}')", av_err2str(ret));
return false;
}
#endif
ret = avfilter_init_dict(buffersink_ctx, nullptr);
if (ret < 0) {
log_error("Cannot initialize audio buffer sink (error '{}')", av_err2str(ret));
return false;
}
return true;
}
int FilteringContext::encodeWriteFrame(
AVFormatContext* formatContext,
bool flush)
{
AVFrame* filteredFrame = flush ? nullptr : this->filtered_frame;
int ret;
log_debug("Encoding frame");
av_packet_unref(this->encPacket);
#if (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 0, 0))
// Rescale timestamp if supported
if (filteredFrame && filteredFrame->pts != AV_NOPTS_VALUE)
filteredFrame->pts = av_rescale_q(filteredFrame->pts, filteredFrame->time_base, this->encContext->time_base);
#endif
// STEP 1: Send frame to encoder
ret = avcodec_send_frame(this->encContext, filteredFrame);
if (ret < 0)
return ret;
// STEP 2: Receive encoded packets from encoder
while (ret >= 0) {
// encode filtered frame
ret = avcodec_receive_packet(this->encContext, this->encPacket);
// No more packets available
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
log_debug("No more data");
return 0;
}
if (ret == AVERROR(EINVAL)) {
log_debug("No encoder");
return 0;
}
if (ret < 0) {
log_debug("Error receiving packet (error '{}')", av_err2str(ret));
continue;
}
// prepare packet for muxing
this->encPacket->stream_index = this->streamNumber;
// Set stream index and rescale timestamps
av_packet_rescale_ts(this->encPacket, this->encContext->time_base, formatContext->streams[this->streamNumber]->time_base);
log_debug("rescale #{} pts {} - dts {} - duration {}\n timebase {}/{} -> {}/{}", this->encPacket->stream_index, this->encPacket->pts, this->encPacket->dts, this->encPacket->duration, this->encContext->time_base.den, this->encContext->time_base.num, formatContext->streams[this->streamNumber]->time_base.den, formatContext->streams[this->streamNumber]->time_base.num);
log_debug("Muxing frame");
// STEP 3: Write to output file/buffer
// mux encoded frame
ret = av_interleaved_write_frame(formatContext, this->encPacket);
}
return ret;
}
int FilteringContext::filterEncodeWriteFrame(
AVFrame* frame,
AVFormatContext* formatContext)
{
int ret;
log_debug("Pushing decoded frame to filters");
// push the decoded frame into the filtergraph
ret = av_buffersrc_add_frame_flags(this->buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF);
if (ret < 0) {
log_error("Error while feeding the filtergraph (error '{}')", av_err2str(ret));
return ret;
}
// pull filtered frames from the filtergraph
while (true) {
log_debug("Pulling filtered frame from filters");
ret = av_buffersink_get_frame(this->buffersink_ctx, this->filtered_frame);
if (ret < 0) {
/* if no more frames for output - returns AVERROR(EAGAIN)
* if flushed and no more frames for output - returns AVERROR_EOF
* rewrite retcode to 0 to show it as normal procedure completion
*/
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
ret = 0;
break;
}
#if (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 0, 0))
this->filtered_frame->time_base = av_buffersink_get_time_base(this->buffersink_ctx);
#endif
this->filtered_frame->pict_type = AV_PICTURE_TYPE_NONE;
ret = this->encodeWriteFrame(formatContext, false);
av_frame_unref(this->filtered_frame);
if (ret < 0)
break;
}
return ret;
}
StreamContext::StreamContext(
AVStream* stream,
std::size_t streamNumber,
AVFormatContext* inFormatContext,
const std::shared_ptr<TranscodingProfile>& profile)
: stream(stream)
, streamNumber(streamNumber)
{
// STEP 1: Find and allocate decoder
const AVCodec* dec = avcodec_find_decoder(as_codecpar(stream)->codec_id);
if (!dec) {
log_error("Failed to find decoder for stream #{}", streamNumber);
return;
}
this->dec_ctx = avcodec_alloc_context3(dec);
if (!this->dec_ctx) {
log_error("Failed to allocate the decoder context for stream #{}", streamNumber);
return;
}
// STEP 2: Copy codec parameters to decoder context
int ret = avcodec_parameters_to_context(this->dec_ctx, as_codecpar(stream));
if (ret < 0) {
log_error("Failed to copy decoder parameters to input decoder context for stream #{} (error '{}')", streamNumber, av_err2str(ret));
return;
}
/* Inform the decoder about the timebase for the packet timestamps.
* This is highly recommended, but not mandatory. */
this->dec_ctx->pkt_timebase = stream->time_base;
// STEP 3: Open decoder for audio/video
// Reencode video & audio and remux subtitles etc.
if (this->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
|| this->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
if (this->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
this->dec_ctx->framerate = av_guess_frame_rate(inFormatContext, stream, nullptr);
/* Open decoder */
ret = avcodec_open2(this->dec_ctx, dec, nullptr);
if (ret < 0) {
log_error("Failed to open decoder for stream #{} (error '{}')", streamNumber, av_err2str(ret));
return;
}
}
// STEP 4: Allocate frame buffer
this->dec_frame = av_frame_alloc();
if (!this->dec_frame) {
log_error("Frame alloc failed");
return;
}
// STEP 5: Set default transcoding settings (same as input)
/* as default, we choose transcoding to same codec */
settings.codecId = dec_ctx->codec_id;
/* as default, we transcode to same properties (picture size,
* sample rate etc.). These properties can be changed for output
* streams easily using filters */
settings.height = dec_ctx->height;
settings.width = dec_ctx->width;
settings.aspectRatio = dec_ctx->sample_aspect_ratio;
settings.pixelFormat = dec_ctx->pix_fmt;
settings.frameRate = dec_ctx->framerate;
settings.sampleRate = dec_ctx->sample_rate;
settings.sampleFormat = dec_ctx->sample_fmt;
// STEP 6: Override with profile settings if set
if (profile->getSampleFreq() != SOURCE)
settings.sampleRate = profile->getSampleFreq(); // fix with bits_per_sample
if (profile->getNumChannels() != SOURCE)
settings.numChannels = profile->getNumChannels();
if (profile->encoder.getWidth() != SOURCE)
settings.width = profile->encoder.getWidth();
if (profile->encoder.getHeight() != SOURCE)
settings.height = profile->encoder.getHeight();
// STEP 7: Override encoder codec if specified
if (this->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO && !profile->encoder.getACodec().empty()) {
auto codec = avcodec_find_encoder_by_name(profile->encoder.getACodec().c_str());
if (codec)
settings.codecId = codec->id;
else
log_error("Audio encoder '{}' not found", profile->encoder.getACodec());
} else if (this->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO && !profile->encoder.getVCodec().empty()) {
auto codec = avcodec_find_encoder_by_name(profile->encoder.getVCodec().c_str());
if (codec)
settings.codecId = codec->id;
}
}
StreamContext::~StreamContext()
{
log_debug("cleanup");
if (dec_ctx)
avcodec_free_context(&dec_ctx);
if (enc_ctx)
avcodec_free_context(&enc_ctx);
if (dec_frame)
av_frame_free(&dec_frame);
dec_ctx = nullptr;
enc_ctx = nullptr;
dec_frame = nullptr;
}
int StreamContext::receiveFrame()
{
return avcodec_receive_frame(this->dec_ctx, this->dec_frame);
}
int StreamContext::sendPacket(AVPacket* packet)
{
return avcodec_send_packet(this->dec_ctx, packet);
}
int StreamContext::createEncoder(
AVStream* out_stream,
AVFormatContext* outFormatContext)
{
int ret = 0;
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
|| dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
// STEP 1: Find encoder by codec ID
const AVCodec* encoder = avcodec_find_encoder(settings.codecId);
if (!encoder) {
log_error("Necessary encoder {} not found", settings.codecId);
return AVERROR_INVALIDDATA;
}
// STEP 2: Allocate encoder context
enc_ctx = avcodec_alloc_context3(encoder);
if (!enc_ctx) {
log_error("Failed to allocate the encoder context");
return AVERROR(ENOMEM);
}
// STEP 3: Configure encoder for VIDEO
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
enc_ctx->height = settings.height;
enc_ctx->width = settings.width;
enc_ctx->sample_aspect_ratio = settings.aspectRatio;
// Set first supported pixel format
#if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 13, 100))
/* take first format from list of supported formats */
if (encoder->pix_fmts)
enc_ctx->pix_fmt = encoder->pix_fmts[0];
else
enc_ctx->pix_fmt = dec_ctx->pix_fmt;
#else
const enum AVPixelFormat* pix_fmts = nullptr;
ret = avcodec_get_supported_config(enc_ctx, nullptr,
AV_CODEC_CONFIG_PIX_FORMAT, 0,
(const void**)&pix_fmts, nullptr);
/* take first format from list of supported formats */
enc_ctx->pix_fmt = (ret >= 0 && pix_fmts) ? pix_fmts[0] : settings.pixelFormat;
#endif
/* video time_base can be set to whatever is handy and supported by encoder */
enc_ctx->time_base = av_inv_q(settings.frameRate);
} else {
// STEP 4: Configure encoder for AUDIO
enc_ctx->sample_rate = settings.sampleRate;
// Channel layout API changes across FFmpeg versions
#if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 0, 0))
enc_ctx->channel_layout = dec_ctx->channel_layout;
enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout);
#else
ret = av_channel_layout_copy(&enc_ctx->ch_layout, &dec_ctx->ch_layout);
if (ret < 0)
return ret;
#endif
// Set first supported sample format
#if (LIBAVCODEC_VERSION_INT < AV_VERSION_INT(61, 13, 100))
if (encoder->sample_fmts)
enc_ctx->sample_fmt = encoder->sample_fmts[0];
#else
const enum AVSampleFormat* sample_fmts = nullptr;
ret = avcodec_get_supported_config(enc_ctx, nullptr,
AV_CODEC_CONFIG_SAMPLE_FORMAT, 0,
(const void**)&sample_fmts, nullptr);
/* take first format from list of supported formats */
enc_ctx->sample_fmt = (ret >= 0 && sample_fmts) ? sample_fmts[0] : settings.sampleFormat;
#endif
enc_ctx->time_base = (AVRational) { 1, enc_ctx->sample_rate };
}
// STEP 5: Set global header flag if needed
if (outFormatContext->oformat->flags & AVFMT_GLOBALHEADER)
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
// STEP 6: Open the encoder
/* Third parameter can be used to pass settings to encoder */
ret = avcodec_open2(enc_ctx, encoder, nullptr);
if (ret < 0) {
log_error("Cannot open {} encoder for stream #{} (error '{}')", encoder->name, streamNumber, av_err2str(ret));
return ret;
}
// STEP 7: Copy encoder parameters to output stream
ret = avcodec_parameters_from_context(as_codecpar(out_stream), enc_ctx);
if (ret < 0) {
log_error("Failed to copy encoder parameters to output stream #{} (error '{}')", streamNumber, av_err2str(ret));
return ret;
}
out_stream->time_base = enc_ctx->time_base;
} else if (dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN) {
log_error("Elementary stream #{} is of unknown type, cannot proceed", streamNumber);
return AVERROR_INVALIDDATA;
} else {
// For subtitle/other streams, just copy parameters
// if this stream must be remuxed
ret = avcodec_parameters_copy(as_codecpar(out_stream), as_codecpar(stream));
if (ret < 0) {
log_error("Copying parameters for stream #{} failed (error '{}')", streamNumber, av_err2str(ret));
return ret;
}
out_stream->time_base = stream->time_base;
}
return ret;
}
#endif