add force transcoding to AAC

This commit is contained in:
Alex 2023-12-10 03:09:01 +08:00
parent 00e6ca3f79
commit 8ffad9c16a
11 changed files with 755 additions and 110 deletions

View File

@ -9,14 +9,9 @@
*/ */
#if defined(ENABLE_FFMPEG) #if defined(ENABLE_FFMPEG)
#if !defined(_WIN32)
#include <dlfcn.h>
#endif
#include "Util/File.h"
#include "Util/uv_errno.h"
#include "Transcode.h" #include "Transcode.h"
#include "FFmpeg/Utils.h"
#include "Extension/AAC.h" #include "Extension/AAC.h"
#include "Common/config.h"
#define MAX_DELAY_SECOND 3 #define MAX_DELAY_SECOND 3
using namespace std; using namespace std;
@ -24,91 +19,7 @@ using namespace toolkit;
namespace mediakit { namespace mediakit {
static string ffmpeg_err(int errnum) {
char errbuf[AV_ERROR_MAX_STRING_SIZE];
av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE);
return errbuf;
}
std::shared_ptr<AVPacket> alloc_av_packet() {
auto pkt = std::shared_ptr<AVPacket>(av_packet_alloc(), [](AVPacket *pkt) {
av_packet_free(&pkt);
});
pkt->data = NULL; // packet data will be allocated by the encoder
pkt->size = 0;
return pkt;
}
//////////////////////////////////////////////////////////////////////////////////////////
static void on_ffmpeg_log(void *ctx, int level, const char *fmt, va_list args) {
GET_CONFIG(bool, enable_ffmpeg_log, General::kEnableFFmpegLog);
if (!enable_ffmpeg_log) {
return;
}
LogLevel lev;
switch (level) {
case AV_LOG_FATAL: lev = LError; break;
case AV_LOG_ERROR: lev = LError; break;
case AV_LOG_WARNING: lev = LWarn; break;
case AV_LOG_INFO: lev = LInfo; break;
case AV_LOG_VERBOSE: lev = LDebug; break;
case AV_LOG_DEBUG: lev = LDebug; break;
case AV_LOG_TRACE: lev = LTrace; break;
default: lev = LTrace; break;
}
LoggerWrapper::printLogV(::toolkit::getLogger(), lev, __FILE__, ctx ? av_default_item_name(ctx) : "NULL", level, fmt, args);
}
static bool setupFFmpeg_l() {
av_log_set_level(AV_LOG_TRACE);
av_log_set_flags(AV_LOG_PRINT_LEVEL);
av_log_set_callback(on_ffmpeg_log);
#if (LIBAVCODEC_VERSION_MAJOR < 58)
avcodec_register_all();
#endif
return true;
}
static void setupFFmpeg() {
static auto flag = setupFFmpeg_l();
}
static bool checkIfSupportedNvidia_l() {
#if !defined(_WIN32)
GET_CONFIG(bool, check_nvidia_dev, General::kCheckNvidiaDev);
if (!check_nvidia_dev) {
return false;
}
auto so = dlopen("libnvcuvid.so.1", RTLD_LAZY);
if (!so) {
WarnL << "libnvcuvid.so.1加载失败:" << get_uv_errmsg();
return false;
}
dlclose(so);
bool find_driver = false;
File::scanDir("/dev", [&](const string &path, bool is_dir) {
if (!is_dir && start_with(path, "/dev/nvidia")) {
//找到nvidia的驱动
find_driver = true;
return false;
}
return true;
}, false);
if (!find_driver) {
WarnL << "英伟达硬件编解码器驱动文件 /dev/nvidia* 不存在";
}
return find_driver;
#else
return false;
#endif
}
static bool checkIfSupportedNvidia() {
static auto ret = checkIfSupportedNvidia_l();
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
@ -316,7 +227,7 @@ static inline const AVCodec *getCodecByName(const std::vector<std::string> &code
} }
FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std::vector<std::string> &codec_name) { FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std::vector<std::string> &codec_name) {
setupFFmpeg(); ffmpeg::setupFFmpeg();
const AVCodec *codec = nullptr; const AVCodec *codec = nullptr;
const AVCodec *codec_default = nullptr; const AVCodec *codec_default = nullptr;
if (!codec_name.empty()) { if (!codec_name.empty()) {
@ -328,7 +239,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std:
if (codec && codec->id == AV_CODEC_ID_H264) { if (codec && codec->id == AV_CODEC_ID_H264) {
break; break;
} }
if (checkIfSupportedNvidia()) { if (ffmpeg::checkIfSupportedNvidia()) {
codec = getCodec({{"libopenh264"}, {AV_CODEC_ID_H264}, {"h264_qsv"}, {"h264_videotoolbox"}, {"h264_cuvid"}, {"h264_nvmpi"}}); codec = getCodec({{"libopenh264"}, {AV_CODEC_ID_H264}, {"h264_qsv"}, {"h264_videotoolbox"}, {"h264_cuvid"}, {"h264_nvmpi"}});
} else { } else {
codec = getCodec({{"libopenh264"}, {AV_CODEC_ID_H264}, {"h264_qsv"}, {"h264_videotoolbox"}, {"h264_nvmpi"}}); codec = getCodec({{"libopenh264"}, {AV_CODEC_ID_H264}, {"h264_qsv"}, {"h264_videotoolbox"}, {"h264_nvmpi"}});
@ -339,7 +250,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std:
if (codec && codec->id == AV_CODEC_ID_HEVC) { if (codec && codec->id == AV_CODEC_ID_HEVC) {
break; break;
} }
if (checkIfSupportedNvidia()) { if (ffmpeg::checkIfSupportedNvidia()) {
codec = getCodec({{AV_CODEC_ID_HEVC}, {"hevc_qsv"}, {"hevc_videotoolbox"}, {"hevc_cuvid"}, {"hevc_nvmpi"}}); codec = getCodec({{AV_CODEC_ID_HEVC}, {"hevc_qsv"}, {"hevc_videotoolbox"}, {"hevc_cuvid"}, {"hevc_nvmpi"}});
} else { } else {
codec = getCodec({{AV_CODEC_ID_HEVC}, {"hevc_qsv"}, {"hevc_videotoolbox"}, {"hevc_nvmpi"}}); codec = getCodec({{AV_CODEC_ID_HEVC}, {"hevc_qsv"}, {"hevc_videotoolbox"}, {"hevc_nvmpi"}});
@ -456,11 +367,11 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std:
if (codec_default && codec_default != codec) { if (codec_default && codec_default != codec) {
//硬件编解码器打开失败,尝试软件的 //硬件编解码器打开失败,尝试软件的
WarnL << "打开解码器" << codec->name << "失败,原因是:" << ffmpeg_err(ret) << ", 再尝试打开解码器" << codec_default->name; WarnL << "打开解码器" << codec->name << "失败,原因是:" << ffmpeg::ffmpeg_err(ret) << ", 再尝试打开解码器" << codec_default->name;
codec = codec_default; codec = codec_default;
continue; continue;
} }
throw std::runtime_error(StrPrinter << "打开解码器" << codec->name << "失败:" << ffmpeg_err(ret)); throw std::runtime_error(StrPrinter << "打开解码器" << codec->name << "失败:" << ffmpeg::ffmpeg_err(ret));
} }
} }
@ -484,7 +395,7 @@ void FFmpegDecoder::flush() {
break; break;
} }
if (ret < 0) { if (ret < 0) {
WarnL << "avcodec_receive_frame failed:" << ffmpeg_err(ret); WarnL << "avcodec_receive_frame failed:" << ffmpeg::ffmpeg_err(ret);
break; break;
} }
onDecode(out_frame); onDecode(out_frame);
@ -526,7 +437,7 @@ bool FFmpegDecoder::inputFrame(const Frame::Ptr &frame, bool live, bool async, b
bool FFmpegDecoder::decodeFrame(const char *data, size_t size, uint64_t dts, uint64_t pts, bool live, bool key_frame) { bool FFmpegDecoder::decodeFrame(const char *data, size_t size, uint64_t dts, uint64_t pts, bool live, bool key_frame) {
TimeTicker2(30, TraceL); TimeTicker2(30, TraceL);
auto pkt = alloc_av_packet(); auto pkt = ffmpeg::alloc_av_packet();
pkt->data = (uint8_t *) data; pkt->data = (uint8_t *) data;
pkt->size = size; pkt->size = size;
pkt->dts = dts; pkt->dts = dts;
@ -538,7 +449,7 @@ bool FFmpegDecoder::decodeFrame(const char *data, size_t size, uint64_t dts, uin
auto ret = avcodec_send_packet(_context.get(), pkt.get()); auto ret = avcodec_send_packet(_context.get(), pkt.get());
if (ret < 0) { if (ret < 0) {
if (ret != AVERROR_INVALIDDATA) { if (ret != AVERROR_INVALIDDATA) {
WarnL << "avcodec_send_packet failed:" << ffmpeg_err(ret); WarnL << "avcodec_send_packet failed:" << ffmpeg::ffmpeg_err(ret);
} }
return false; return false;
} }
@ -550,7 +461,7 @@ bool FFmpegDecoder::decodeFrame(const char *data, size_t size, uint64_t dts, uin
break; break;
} }
if (ret < 0) { if (ret < 0) {
WarnL << "avcodec_receive_frame failed:" << ffmpeg_err(ret); WarnL << "avcodec_receive_frame failed:" << ffmpeg::ffmpeg_err(ret);
break; break;
} }
if (live && pts - out_frame->get()->pts > MAX_DELAY_SECOND * 1000 && _ticker.createdTime() > 10 * 1000) { if (live && pts - out_frame->get()->pts > MAX_DELAY_SECOND * 1000 && _ticker.createdTime() > 10 * 1000) {
@ -614,7 +525,7 @@ FFmpegFrame::Ptr FFmpegSwr::inputFrame(const FFmpegFrame::Ptr &frame) {
int ret = 0; int ret = 0;
if (0 != (ret = swr_convert_frame(_ctx, out->get(), frame->get()))) { if (0 != (ret = swr_convert_frame(_ctx, out->get(), frame->get()))) {
WarnL << "swr_convert_frame failed:" << ffmpeg_err(ret); WarnL << "swr_convert_frame failed:" << ffmpeg::ffmpeg_err(ret);
return nullptr; return nullptr;
} }
return out; return out;
@ -680,7 +591,7 @@ FFmpegFrame::Ptr FFmpegSws::inputFrame(const FFmpegFrame::Ptr &frame, int &ret,
} }
} }
if (0 >= (ret = sws_scale(_ctx, frame->get()->data, frame->get()->linesize, 0, frame->get()->height, out->get()->data, out->get()->linesize))) { if (0 >= (ret = sws_scale(_ctx, frame->get()->data, frame->get()->linesize, 0, frame->get()->height, out->get()->data, out->get()->linesize))) {
WarnL << "sws_scale failed:" << ffmpeg_err(ret); WarnL << "sws_scale failed:" << ffmpeg::ffmpeg_err(ret);
return nullptr; return nullptr;
} }

View File

@ -356,6 +356,7 @@ const string kBenchmarkMode = "benchmark_mode";
const string kWaitTrackReady = "wait_track_ready"; const string kWaitTrackReady = "wait_track_ready";
const string kPlayTrack = "play_track"; const string kPlayTrack = "play_track";
const string kProxyUrl = "proxy_url"; const string kProxyUrl = "proxy_url";
const string kForceToAac = "force_to_aac";
} // namespace Client } // namespace Client
} // namespace mediakit } // namespace mediakit

View File

@ -379,34 +379,54 @@ extern const std::string kGopCache;
* rtsp/rtmp播放器 * rtsp/rtmp播放器
* *
* *
* rtsp/rtmp Player/Pusher instance config
* these config not for ini file
* just for PlayerBase/PusherBase instance
*/ */
namespace Client { namespace Client {
// 指定网卡ip // 指定网卡ip
// Setting network card ip
extern const std::string kNetAdapter; extern const std::string kNetAdapter;
// 设置rtp传输类型可选项有0(tcp默认)、1(udp)、2(组播) // 设置rtp传输类型可选项有0(tcp默认)、1(udp)、2(组播)
// 设置方法:player[PlayerBase::kRtpType] = 0/1/2; // 设置方法:player[PlayerBase::kRtpType] = 0/1/2;
// Setting rtp transmission type, optional 0 (tcp, default), 1 (udp), 2 (multicast)
// Setting method: player[PlayerBase::kRtpType] = 0/1/2;
extern const std::string kRtpType; extern const std::string kRtpType;
// rtsp认证用户名 // rtsp认证用户名
// rtsp authentication username
extern const std::string kRtspUser; extern const std::string kRtspUser;
// rtsp认证用用户密码可以是明文也可以是md5,md5密码生成方式 md5(username:realm:password) // rtsp认证用用户密码可以是明文也可以是md5,md5密码生成方式 md5(username:realm:password)
// password are used for RTSP authentication, which can be either plain text or MD5 encrypted. The MD5 password is generated using the following format: md5(username:realm:password).
extern const std::string kRtspPwd; extern const std::string kRtspPwd;
// rtsp认证用用户密码是否为md5类型 // rtsp认证用用户密码是否为md5类型
// Whether the user password used for RTSP authentication is MD5 type
extern const std::string kRtspPwdIsMD5; extern const std::string kRtspPwdIsMD5;
// 握手超时时间默认10,000 毫秒 // 握手超时时间默认10,000 毫秒
// Handshake timeout time, default 10,000 milliseconds
extern const std::string kTimeoutMS; extern const std::string kTimeoutMS;
// rtp/rtmp包接收超时时间默认5000秒 // rtp/rtmp包接收超时时间默认5000秒
// rtp/rtmp packet receive timeout time, default 5000 seconds
extern const std::string kMediaTimeoutMS; extern const std::string kMediaTimeoutMS;
// rtsp/rtmp心跳时间,默认5000毫秒 // rtsp/rtmp心跳时间,默认5000毫秒
// rtsp/rtmp heartbeat time, default 5000 milliseconds
extern const std::string kBeatIntervalMS; extern const std::string kBeatIntervalMS;
// 是否为性能测试模式性能测试模式开启后不会解析rtp或rtmp包 // 是否为性能测试模式性能测试模式开启后不会解析rtp或rtmp包
// Whether it is performance test mode, rtp or rtmp packet will not be parsed after performance test mode is turned on
extern const std::string kBenchmarkMode; extern const std::string kBenchmarkMode;
// 播放器在触发播放成功事件时是否等待所有track ready时再回调 // 播放器在触发播放成功事件时是否等待所有track ready时再回调
// Should the player wait for all tracks to be ready before triggering the playback success event callback?
extern const std::string kWaitTrackReady; extern const std::string kWaitTrackReady;
// rtsp播放指定track可选项有0(不指定,默认)、1(视频)、2(音频) // rtsp播放指定track可选项有0(不指定,默认)、1(视频)、2(音频)
// 设置方法:player[Client::kPlayTrack] = 0/1/2; // 设置方法:player[Client::kPlayTrack] = 0/1/2;
// rtsp play specified track, optional 0 (not specified, default), 1 (video), 2 (audio)
// Setting method: player[Client::kPlayTrack] = 0/1/2;
extern const std::string kPlayTrack; extern const std::string kPlayTrack;
//设置代理url目前只支持http协议 //设置代理url目前只支持http协议
// Setting proxy url, currently only http protocol is supported
extern const std::string kProxyUrl; extern const std::string kProxyUrl;
//强制转码为AAC
// Force transcoding to AAC
extern const std::string kForceToAac;
} // namespace Client } // namespace Client
} // namespace mediakit } // namespace mediakit

View File

@ -0,0 +1,339 @@
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#if defined(ENABLE_FFMPEG)
#include "AudioTranscoder.h"
using namespace toolkit;
namespace mediakit {
static int getSampleRateIndex(int sample_rate) {
static int sample_rates[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 };
int num_sample_rates = sizeof(sample_rates) / sizeof(sample_rates[0]);
for (int i = 0; i < num_sample_rates; i++) {
if (sample_rate == sample_rates[i]) {
return i;
}
}
// 如果没有找到返回0
// Default to index 0 if sample rate is not found
return 0;
}
static uint8_t *addADTSHeader(AVPacket *packet, int sample_rate, int channel_count) {
int adts_size = 7;
int packet_size = packet->size + adts_size;
auto new_data = (uint8_t *)av_malloc(packet_size);
// ADTS header
int profile = FF_PROFILE_AAC_LOW; // AAC LC
int freq_index = getSampleRateIndex(sample_rate);
int channel_config = channel_count;
new_data[0] = 0xFF; // syncword
new_data[1] = 0xF1; // MPEG-4, AAC-LC
new_data[2] = (profile << 6) | (freq_index << 2) | (channel_config >> 2);
new_data[3] = ((channel_config & 3) << 6) | (packet_size >> 11);
new_data[4] = (packet_size >> 3) & 0xFF;
new_data[5] = ((packet_size & 7) << 5) | 0x1F;
new_data[6] = 0xFC;
// 复制ADTS头后的原始包数据
// Copy the original packet data after the ADTS header
memcpy(new_data + adts_size, packet->data, packet->size);
return new_data;
}
AudioTranscoder::~AudioTranscoder() {
cleanup();
}
void AudioTranscoder::inputAudioData(const uint8_t *data, size_t size, int64_t pts, int64_t dts) {
if (_release || !_decoder_ctx) {
return;
}
auto packet = ffmpeg::alloc_av_packet();
packet->data = const_cast<uint8_t *>(data);
packet->size = (int)size;
int ret = avcodec_send_packet(_decoder_ctx.get(), packet.get());
if (ret < 0) {
if (ret == AVERROR(EAGAIN)) {
// Decoder is not ready to accept new data, try again later
ErrorL << "Decoder not ready to accept new data, try again later";
return;
} else if (ret == AVERROR_EOF) {
// 输入流结束,刷新解码器
// End of input stream, flush the decoder
avcodec_send_packet(_decoder_ctx.get(), nullptr);
} else {
ErrorL << "Failed to send packet to decoder, error: " << ret;
return;
}
}
while (ret >= 0 && !_release) {
AVFrame *frame = av_frame_alloc();
ret = avcodec_receive_frame(_decoder_ctx.get(), frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
av_frame_free(&frame);
break;
} else if (ret < 0) {
ErrorL << "Error receiving frame from decoder: " << ret;
av_frame_free(&frame);
return;
}
if (!_encoder_ctx) {
if (!initEncodeCodec(
frame->sample_rate, frame->ch_layout.nb_channels, _decoder_ctx->bit_rate, AV_CODEC_ID_AAC)) {
ErrorL << "Failed to init encode codec";
return;
}
}
transcodeFrame(frame, pts, dts);
}
}
// 销毁所有资源
// Destroy all resources
void AudioTranscoder::release() {
_release = true;
_callback = nullptr;
}
void AudioTranscoder::cleanup() {
_callback = nullptr;
av_channel_layout_uninit(&_audio_params_ch_layout);
if (_decoder_ctx) {
_decoder_ctx.reset();
}
if (_encoder_ctx) {
_encoder_ctx.reset();
}
if (_swr_ctx) {
_swr_ctx.reset();
}
if (_fifo) {
_fifo.reset();
}
}
int AudioTranscoder::init_re_sampler(AVFrame *pFrame) {
int ret = 0;
if (pFrame->format != _audio_params_fmt|| pFrame->sample_rate != _audio_params_freq
|| av_channel_layout_compare(&pFrame->ch_layout, &_audio_params_ch_layout) || !_swr_ctx) {
_swr_ctx.reset();
InfoL << "init resample context";
SwrContext *resample_context = nullptr;
ret = swr_alloc_set_opts2(
&resample_context, &_encoder_ctx->ch_layout, _encoder_ctx->sample_fmt, _encoder_ctx->sample_rate, &_decoder_ctx->ch_layout,
_decoder_ctx->sample_fmt, _decoder_ctx->sample_rate, 0, nullptr);
if (ret < 0) {
ErrorL << "Failed to allocate resample context: " << ret;
return ret;
}
// 打开重采样器
// Open the resampler with the specified parameters.
if ((ret = swr_init(resample_context)) < 0) {
ErrorL << "Could not open resample context: " << ret;
swr_free(&resample_context);
return ret;
}
if (av_channel_layout_copy(&_audio_params_ch_layout, &pFrame->ch_layout) < 0) {
swr_free(&resample_context);
ErrorL << "Failed to copy channel layout";
return -1;
}
_audio_params_freq = pFrame->sample_rate;
_audio_params_fmt= pFrame->format;
_swr_ctx.reset(resample_context);
}
for (int i = 0; i < _audio_params_ch_layout.nb_channels; i++) {
if (!pFrame->extended_data[i]) {
ErrorL << "extended_data[" << i << "] is null";
return -1;
}
}
return ret;
}
bool AudioTranscoder::initDecodeCodec(int psi_codec_id) {
AVCodecID codec_id = ffmpeg::psi_to_avcodec_id(psi_codec_id);
if (codec_id == AV_CODEC_ID_NONE) {
ErrorL << "Codec ID not found for PSI codec ID: " << psi_codec_id;
return false;
}
if (_decoder_ctx && _decoder_ctx->codec_id == codec_id) {
return true;
}
const AVCodec *codec = avcodec_find_decoder(codec_id);
if (!codec) {
ErrorL << "Decoder not found for codec ID: " << codec_id;
return false;
}
_decoder_ctx.reset(avcodec_alloc_context3(codec));
if (!_decoder_ctx) {
ErrorL << "Failed to allocate decoder context";
return false;
}
if (avcodec_open2(_decoder_ctx.get(), codec, nullptr) < 0) {
ErrorL << "Failed to open decoder";
return false;
}
return true;
}
bool AudioTranscoder::initEncodeCodec(int sample_rate, int channels, int bit_rate, AVCodecID codec_id) {
if (_encoder_ctx && _encoder_ctx->codec_id == codec_id) {
return true;
}
auto codec = avcodec_find_encoder(codec_id);
if (!codec) {
ErrorL << "Encoder not found for codec ID: " << codec_id;
return false;
}
_encoder_ctx.reset(avcodec_alloc_context3(codec));
if (!_encoder_ctx) {
ErrorL << "Failed to allocate encoder context";
return false;
}
_encoder_ctx->sample_rate = sample_rate;
_encoder_ctx->bit_rate = bit_rate;
// _encoder_ctx->sample_fmt = codec->sample_fmts[0];
_encoder_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
_encoder_ctx->time_base = (AVRational) { 1, sample_rate };
_encoder_ctx->profile = FF_PROFILE_AAC_LOW;
av_channel_layout_default(&_encoder_ctx->ch_layout, channels);
if (avcodec_open2(_encoder_ctx.get(), codec, nullptr) < 0) {
ErrorL << "Failed to open encoder";
return false;
}
return true;
}
void AudioTranscoder::transcodeFrame(AVFrame *inputFrame, int64_t pts, int64_t dts) {
if (_audio_params_freq <= 0) {
if (av_channel_layout_copy(&_audio_params_ch_layout, &_decoder_ctx->ch_layout) < 0) {
return;
}
_audio_params_freq = _decoder_ctx->sample_rate;
_audio_params_fmt= _decoder_ctx->sample_fmt;
}
int ret = 0;
if (inputFrame->format != _audio_params_fmt || inputFrame->sample_rate != _audio_params_freq
|| av_channel_layout_compare(&inputFrame->ch_layout, &_audio_params_ch_layout) || !_fifo) {
_fifo.reset(av_audio_fifo_alloc(_encoder_ctx->sample_fmt, _encoder_ctx->ch_layout.nb_channels, 1));
InfoL << "init fifo";
if (!_fifo) {
ErrorL << "Failed to allocate FIFO";
return;
}
}
if (init_re_sampler(inputFrame) < 0) {
ErrorL << "Failed to init resampler";
return;
}
uint8_t **converted_input_samples = nullptr;
ret = av_samples_alloc_array_and_samples(
&converted_input_samples, nullptr, _encoder_ctx->ch_layout.nb_channels, inputFrame->nb_samples, _encoder_ctx->sample_fmt, 0);
if (ret < 0) {
ErrorL << "Could not allocate converted input samples, error: " << ret;
return;
}
// converted_input_samples is used as a temporary storage for the converted input samples
std::unique_ptr<uint8_t *, std::function<void(uint8_t **)>> converted_input_samples_ptr(converted_input_samples, [](uint8_t **p) {
if (p) {
av_freep(&p[0]);
av_freep(&p);
}
});
// 转换输入样本为期望的输出样本格式
// 这需要一个由converted_input_samples提供的临时存储
// Convert the input samples to the desired output sample format.
// This requires a temporary storage provided by converted_input_samples.
ret = swr_convert(
_swr_ctx.get(), converted_input_samples_ptr.get(), inputFrame->nb_samples, (const uint8_t **)inputFrame->extended_data, inputFrame->nb_samples);
if (ret < 0) {
ErrorL << "Could not convert input samples";
converted_input_samples_ptr.reset();
return;
}
if (av_audio_fifo_realloc(_fifo.get(), av_audio_fifo_size(_fifo.get()) + inputFrame->nb_samples) < 0) {
ErrorL << "Could not reallocate FIFO";
converted_input_samples_ptr.reset();
return;
}
if (av_audio_fifo_write(_fifo.get(), (void **)converted_input_samples_ptr.get(), inputFrame->nb_samples) < inputFrame->nb_samples) {
ErrorL << "Could not write data to FIFO";
converted_input_samples_ptr.reset();
return;
}
converted_input_samples_ptr.reset();
av_frame_free(&inputFrame);
const int output_frame_size = _encoder_ctx->frame_size;
// 读取FIFO缓冲区中的数据并将其传递给回调函数
// Read data from FIFO buffer and pass it to the callback function
while (av_audio_fifo_size(_fifo.get()) >= output_frame_size) {
AVFrame *outputFrame = av_frame_alloc();
if (!outputFrame) {
ErrorL << "Failed to allocate output frame";
return;
}
const int frame_size = FFMIN(av_audio_fifo_size(_fifo.get()), output_frame_size);
outputFrame->nb_samples = frame_size;
av_channel_layout_copy(&outputFrame->ch_layout, &_encoder_ctx->ch_layout);
outputFrame->format = _encoder_ctx->sample_fmt;
outputFrame->sample_rate = _encoder_ctx->sample_rate;
ret = av_frame_get_buffer(outputFrame, 0);
if (ret < 0) {
ErrorL << "Failed to allocate output frame samples: " << ret;
av_frame_free(&outputFrame);
return;
}
ret = av_audio_fifo_read(_fifo.get(), (void **)outputFrame->data, frame_size);
if (ret < frame_size) {
ErrorL << "Failed to read audio data from FIFO buffer: " << ret;
av_frame_free(&outputFrame);
return;
}
if (avcodec_send_frame(_encoder_ctx.get(), outputFrame) < 0) {
ErrorL << "Failed to send frame to encoder";
av_frame_free(&outputFrame);
return;
}
AVPacket *output_packet = av_packet_alloc();
ret = avcodec_receive_packet(_encoder_ctx.get(), output_packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
av_packet_free(&output_packet);
av_frame_free(&outputFrame);
break;
} else if (ret < 0) {
ErrorL << "Error receiving packet from encoder: " << ret;
av_packet_free(&output_packet);
av_frame_free(&outputFrame);
return;
}
auto outBuff = addADTSHeader(output_packet, _encoder_ctx->sample_rate, _encoder_ctx->ch_layout.nb_channels);
onOutputAudioData(outBuff, output_packet->size + 7, pts, dts);
av_freep(&outBuff);
av_packet_free(&output_packet);
av_frame_free(&outputFrame);
}
}
void AudioTranscoder::onOutputAudioData(const uint8_t *data, int size, int64_t pts, int64_t dts) {
if (_callback) {
_callback(data, size, pts, dts);
}
}
void AudioTranscoder::setOnOutputAudioData(const AudioTranscoder::onTranscodeCallback &cb) {
_callback = cb;
}
} // namespace mediakit
#endif // defined(ENABLE_FFMPEG)

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_AUDIOTRANSCODER_H
#define ZLMEDIAKIT_AUDIOTRANSCODER_H
#if defined(ENABLE_FFMPEG)
#include "FFmpeg/Utils.h"
namespace mediakit {
class AudioTranscoder {
public:
using Ptr = std::shared_ptr<AudioTranscoder>;
using onTranscodeCallback = std::function<void(const uint8_t *data, int size, int64_t pts, int64_t dts)>;
AudioTranscoder() {
_decoder_ctx = std::unique_ptr<AVCodecContext, std::function<void(AVCodecContext *)>>(nullptr, [](AVCodecContext *p) {
if (p) {
avcodec_free_context(&p);
}
});
_encoder_ctx = std::unique_ptr<AVCodecContext, std::function<void(AVCodecContext *)>>(nullptr, [](AVCodecContext *p) {
if (p) {
avcodec_free_context(&p);
}
});
_swr_ctx = std::unique_ptr<SwrContext, std::function<void(SwrContext *)>>(nullptr, [](SwrContext *p) {
if (p) {
swr_free(&p);
}
});
_fifo = std::unique_ptr<AVAudioFifo, std::function<void(AVAudioFifo *)>>(nullptr, [](AVAudioFifo *p) {
if (p) {
av_audio_fifo_free(p);
}
});
}
~AudioTranscoder();
void setOnOutputAudioData(const onTranscodeCallback &cb);
void onOutputAudioData(const uint8_t *data, int size, int64_t pts, int64_t dts);
void inputAudioData(const uint8_t *data, size_t size, int64_t pts, int64_t dts);
bool initDecodeCodec(int psi_codec_id);
bool initEncodeCodec(int sample_rate, int channels, int bit_rate, AVCodecID codec_id=AV_CODEC_ID_AAC);
void release();
protected:
int init_re_sampler(AVFrame *pFrame);
private:
bool _release = false;
int _audio_params_freq = 0;
int _audio_params_fmt = 0;
AVChannelLayout _audio_params_ch_layout = {};
std::unique_ptr<AVCodecContext, std::function<void(AVCodecContext *)>> _decoder_ctx;
std::unique_ptr<AVCodecContext, std::function<void(AVCodecContext *)>> _encoder_ctx;
std::unique_ptr<SwrContext, std::function<void(SwrContext *)>> _swr_ctx;
std::unique_ptr<AVAudioFifo, std::function<void(AVAudioFifo *)>> _fifo;
onTranscodeCallback _callback;
void cleanup();
void transcodeFrame(AVFrame *frame, int64_t i, int64_t i1);
};
} // namespace mediakit
#endif // defined(ENABLE_FFMPEG)
#endif // ZLMEDIAKIT_AUDIOTRANSCODER_H

189
src/FFmpeg/Utils.cpp Normal file
View File

@ -0,0 +1,189 @@
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#if defined(ENABLE_FFMPEG)
#if !defined(_WIN32)
#include <dlfcn.h>
#endif
#include "Common/config.h"
#include "Util/File.h"
#include "Util/uv_errno.h"
#include "Utils.h"
#include "mpeg-proto.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
namespace ffmpeg {
string ffmpeg_err(int errnum) {
char errbuf[AV_ERROR_MAX_STRING_SIZE];
av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE);
return errbuf;
}
std::shared_ptr<AVPacket> alloc_av_packet() {
auto pkt = std::shared_ptr<AVPacket>(av_packet_alloc(), [](AVPacket *pkt) {
// 减少引用计数,避免内存泄漏
av_packet_unref(pkt);
av_packet_free(&pkt);
});
pkt->data = nullptr; // packet data will be allocated by the encoder
pkt->size = 0;
return pkt;
}
//////////////////////////////////////////////////////////////////////////////////////////
static void on_ffmpeg_log(void *ctx, int level, const char *fmt, va_list args) {
GET_CONFIG(bool, enable_ffmpeg_log, General::kEnableFFmpegLog);
if (!enable_ffmpeg_log) {
return;
}
LogLevel lev;
switch (level) {
case AV_LOG_FATAL: lev = LError; break;
case AV_LOG_ERROR: lev = LError; break;
case AV_LOG_WARNING: lev = LWarn; break;
case AV_LOG_INFO: lev = LInfo; break;
case AV_LOG_VERBOSE: lev = LDebug; break;
case AV_LOG_DEBUG: lev = LDebug; break;
case AV_LOG_TRACE: lev = LTrace; break;
default: lev = LTrace; break;
}
LoggerWrapper::printLogV(::toolkit::getLogger(), lev, __FILE__, ctx ? av_default_item_name(ctx) : "NULL", level, fmt, args);
}
static bool setupFFmpeg_l() {
av_log_set_level(AV_LOG_TRACE);
av_log_set_flags(AV_LOG_PRINT_LEVEL);
av_log_set_callback(on_ffmpeg_log);
#if (LIBAVCODEC_VERSION_MAJOR < 58)
avcodec_register_all();
#endif
return true;
}
void setupFFmpeg() {
static auto flag = setupFFmpeg_l();
}
static bool checkIfSupportedNvidia_l() {
#if !defined(_WIN32)
GET_CONFIG(bool, check_nvidia_dev, General::kCheckNvidiaDev);
if (!check_nvidia_dev) {
return false;
}
auto so = dlopen("libnvcuvid.so.1", RTLD_LAZY);
if (!so) {
WarnL << "libnvcuvid.so.1加载失败:" << get_uv_errmsg();
return false;
}
dlclose(so);
bool find_driver = false;
File::scanDir(
"/dev",
[&](const string &path, bool is_dir) {
if (!is_dir && start_with(path, "/dev/nvidia")) {
// 找到nvidia的驱动
find_driver = true;
return false;
}
return true;
},
false);
if (!find_driver) {
WarnL << "英伟达硬件编解码器驱动文件 /dev/nvidia* 不存在";
}
return find_driver;
#else
return false;
#endif
}
bool checkIfSupportedNvidia() {
static auto ret = checkIfSupportedNvidia_l();
return ret;
}
AVCodecID psi_to_avcodec_id(int psi_id) {
switch (psi_id) {
case PSI_STREAM_MPEG1: return AV_CODEC_ID_MPEG1VIDEO;
case PSI_STREAM_MPEG2: return AV_CODEC_ID_MPEG2VIDEO;
case PSI_STREAM_H264: return AV_CODEC_ID_H264;
case PSI_STREAM_H265: return AV_CODEC_ID_HEVC;
case PSI_STREAM_AAC: return AV_CODEC_ID_AAC;
case PSI_STREAM_AUDIO_AC3: return AV_CODEC_ID_AC3;
case PSI_STREAM_AUDIO_DTS: return AV_CODEC_ID_DTS;
case PSI_STREAM_MP3: return AV_CODEC_ID_MP3;
case PSI_STREAM_AUDIO_EAC3: return AV_CODEC_ID_EAC3;
case PSI_STREAM_MPEG4_AAC_LATM: return AV_CODEC_ID_AAC_LATM;
case PSI_STREAM_AUDIO_OPUS: return AV_CODEC_ID_OPUS;
case PSI_STREAM_AUDIO_G711A: return AV_CODEC_ID_PCM_ALAW;
case PSI_STREAM_AUDIO_G711U: return AV_CODEC_ID_PCM_MULAW;
case PSI_STREAM_MPEG4: return AV_CODEC_ID_MPEG4;
case PSI_STREAM_VIDEO_VC1: return AV_CODEC_ID_VC1;
case PSI_STREAM_VIDEO_DIRAC: return AV_CODEC_ID_DIRAC;
case PSI_STREAM_VIDEO_CAVS: return AV_CODEC_ID_CAVS;
case PSI_STREAM_VP8: return AV_CODEC_ID_VP8;
case PSI_STREAM_VP9: return AV_CODEC_ID_VP9;
case PSI_STREAM_AV1: return AV_CODEC_ID_AV1;
case PSI_STREAM_AUDIO_MPEG1: return AV_CODEC_ID_MP2;
default: return (AVCodecID)psi_id;
}
}
int avcodec_id_to_psi(int codec_id) {
switch (codec_id) {
case AV_CODEC_ID_MPEG1VIDEO: return PSI_STREAM_MPEG1;
case AV_CODEC_ID_MPEG2VIDEO: return PSI_STREAM_MPEG2;
case AV_CODEC_ID_H264: return PSI_STREAM_H264;
case AV_CODEC_ID_HEVC: return PSI_STREAM_H265;
case AV_CODEC_ID_AAC: return PSI_STREAM_AAC;
case AV_CODEC_ID_AC3: return PSI_STREAM_AUDIO_AC3;
case AV_CODEC_ID_DTS: return PSI_STREAM_AUDIO_DTS;
case AV_CODEC_ID_MP3: return PSI_STREAM_MP3;
case AV_CODEC_ID_EAC3: return PSI_STREAM_AUDIO_EAC3;
case AV_CODEC_ID_AAC_LATM: return PSI_STREAM_MPEG4_AAC_LATM;
case AV_CODEC_ID_PCM_ALAW: return PSI_STREAM_AUDIO_G711A;
case AV_CODEC_ID_PCM_MULAW: return PSI_STREAM_AUDIO_G711U;
case AV_CODEC_ID_OPUS: return PSI_STREAM_AUDIO_OPUS;
case AV_CODEC_ID_MPEG4: return PSI_STREAM_MPEG4;
case AV_CODEC_ID_VC1: return PSI_STREAM_VIDEO_VC1;
case AV_CODEC_ID_DIRAC: return PSI_STREAM_VIDEO_DIRAC;
case AV_CODEC_ID_CAVS: return PSI_STREAM_VIDEO_CAVS;
case AV_CODEC_ID_VP8: return PSI_STREAM_VP8;
case AV_CODEC_ID_VP9: return PSI_STREAM_VP9;
case AV_CODEC_ID_AV1: return PSI_STREAM_AV1;
default: return codec_id;
}
}
bool is_audio_psi_codec(int psi_codec_id) {
switch (psi_codec_id) {
case PSI_STREAM_AAC:
case PSI_STREAM_MPEG4_AAC:
case PSI_STREAM_MPEG4_AAC_LATM:
case PSI_STREAM_AUDIO_MPEG1:
case PSI_STREAM_MP3:
case PSI_STREAM_AUDIO_AC3:
case PSI_STREAM_AUDIO_DTS:
case PSI_STREAM_AUDIO_EAC3:
case PSI_STREAM_AUDIO_SVAC:
case PSI_STREAM_AUDIO_G711A:
case PSI_STREAM_AUDIO_G711U:
case PSI_STREAM_AUDIO_G722:
case PSI_STREAM_AUDIO_G723:
case PSI_STREAM_AUDIO_G729:
case PSI_STREAM_AUDIO_OPUS: return true;
default: return false;
}
}
} // namespace ffmpeg
} // namespace mediakit
#endif // defined(ENABLE_FFMPEG)

48
src/FFmpeg/Utils.h Normal file
View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_UTILS_H
#define ZLMEDIAKIT_UTILS_H
#if defined(ENABLE_FFMPEG)
#ifdef __cplusplus
#include "Network/Buffer.h"
#include "Util/TimeTicker.h"
#include "Util/logger.h"
extern "C" {
#endif
#include "libavcodec/avcodec.h"
#include "libavcodec/bsf.h"
#include "libavutil/intreadwrite.h"
#include "libswresample/swresample.h"
#include <libavutil/audio_fifo.h>
#include <libavutil/fifo.h>
#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
#ifdef __cplusplus
}
#endif
namespace mediakit {
namespace ffmpeg {
//todo 未来集成FFmpeg的相关工具函数
class Utils {
Utils() = default;
~Utils() = default;
};
void setupFFmpeg();
std::string ffmpeg_err(int err_num);
bool checkIfSupportedNvidia();
std::shared_ptr<AVPacket> alloc_av_packet();
AVCodecID psi_to_avcodec_id(int psi_id);
int avcodec_id_to_psi(int codec_id);
bool is_audio_psi_codec(int psi_codec_id);
} // namespace ffmpeg
} // namespace mediakit
#endif // defined(ENABLE_FFMPEG)
#endif // ZLMEDIAKIT_UTILS_H

View File

@ -433,6 +433,8 @@ HlsPlayerImp::HlsPlayerImp(const EventPoller::Ptr &poller) : PlayerImp<HlsPlayer
void HlsPlayerImp::onPacket(const char *data, size_t len) { void HlsPlayerImp::onPacket(const char *data, size_t len) {
if (!_decoder && _demuxer) { if (!_decoder && _demuxer) {
_decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, _demuxer.get()); _decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, _demuxer.get());
_decoder->setForceToAac((*this)[Client::kForceToAac].as<bool>());
} }
if (_decoder && _demuxer) { if (_decoder && _demuxer) {
@ -450,6 +452,7 @@ void HlsPlayerImp::onPlayResult(const SockException &ex) {
PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(ex); PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(ex);
} else { } else {
auto demuxer = std::make_shared<HlsDemuxer>(); auto demuxer = std::make_shared<HlsDemuxer>();
demuxer->mINI::operator=(*this);
demuxer->start(getPoller(), this); demuxer->start(getPoller(), this);
_demuxer = std::move(demuxer); _demuxer = std::move(demuxer);
} }

View File

@ -23,7 +23,7 @@
namespace mediakit { namespace mediakit {
class HlsDemuxer : public MediaSinkInterface , public TrackSource, public std::enable_shared_from_this<HlsDemuxer> { class HlsDemuxer : public MediaSinkInterface , public TrackSource, public toolkit::mINI, public std::enable_shared_from_this<HlsDemuxer> {
public: public:
HlsDemuxer() = default; HlsDemuxer() = default;
~HlsDemuxer() override { _timer = nullptr; } ~HlsDemuxer() override { _timer = nullptr; }

View File

@ -147,9 +147,28 @@ void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t byt
} }
default: default:
if(codecid != 0){ #if defined(ENABLE_FFMPEG)
WarnL<< "unsupported codec type:" << getCodecName(codecid) << " " << (int)codecid; if (_audioTranscoder && ffmpeg::is_audio_psi_codec(codecid)) {
if (_audioTranscoder->initDecodeCodec(codecid)) {
onTrack(std::make_shared<AACTrack>());
} else {
_audioTranscoder->setOnOutputAudioData(nullptr);
_audioTranscoder->release();
_audioTranscoder.reset();
if (codecid != 0) {
WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int)codecid;
}
}
} else {
if (codecid != 0) {
WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int)codecid;
}
} }
#else
if (codecid != 0) {
WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int)codecid;
}
#endif
break; break;
} }
@ -221,10 +240,24 @@ void DecoderImp::onDecode(int stream,int codecid,int flags,int64_t pts,int64_t d
} }
default: default:
#if defined(ENABLE_FFMPEG)
if (_audioTranscoder && ffmpeg::is_audio_psi_codec(codecid)) {
if (!_tracks[TrackAudio]) {
onTrack(std::make_shared<AACTrack>());
}
_audioTranscoder->inputAudioData((uint8_t *)data, bytes, pts, dts);
} else {
// 海康的 PS 流中会有 codecid 为 0xBD 的包
if (codecid != 0 && codecid != 0xBD) {
WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int)codecid;
}
}
#else
// 海康的 PS 流中会有 codecid 为 0xBD 的包 // 海康的 PS 流中会有 codecid 为 0xBD 的包
if (codecid != 0 && codecid != 0xBD) { if (codecid != 0 && codecid != 0xBD) {
WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int) codecid; WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int)codecid;
} }
#endif
break; break;
} }
} }
@ -244,6 +277,27 @@ void DecoderImp::onTrack(const Track::Ptr &track) {
void DecoderImp::onFrame(const Frame::Ptr &frame) { void DecoderImp::onFrame(const Frame::Ptr &frame) {
_sink->inputFrame(frame); _sink->inputFrame(frame);
} }
void DecoderImp::setForceToAac(bool forceToAac) {
}//namespace mediakit #if defined(ENABLE_FFMPEG)
if (forceToAac) {
_audioTranscoder = std::make_shared<AudioTranscoder>();
_audioTranscoder->setOnOutputAudioData([this](const uint8_t *data, int size, int64_t pts, int64_t dts) {
if (!_tracks[TrackAudio]) {
onTrack(std::make_shared<AACTrack>());
}
onFrame(std::make_shared<FrameFromPtr>(CodecAAC, (char *)data, size, (uint32_t)dts, 0, ADTS_HEADER_LEN));
});
}
#endif
}
DecoderImp::~DecoderImp() {
#if defined(ENABLE_FFMPEG)
if (_audioTranscoder) {
_audioTranscoder->setOnOutputAudioData(nullptr);
_audioTranscoder->release();
_audioTranscoder.reset();
}
#endif
}
} // namespace mediakit

View File

@ -15,10 +15,14 @@
#include <memory> #include <memory>
#include <functional> #include <functional>
#include "Common/MediaSink.h" #include "Common/MediaSink.h"
#include "FFmpeg/AudioTranscoder.h"
#include "Util/mini.h"
#if defined(ENABLE_FFMPEG)
#include "FFmpeg/AudioTranscoder.h"
#endif
namespace mediakit { namespace mediakit {
class Decoder { class Decoder: public toolkit::mINI{
public: public:
using Ptr = std::shared_ptr<Decoder>; using Ptr = std::shared_ptr<Decoder>;
using onDecode = std::function<void(int stream, int codecid, int flags, int64_t pts, int64_t dts, const void *data, size_t bytes)>; using onDecode = std::function<void(int stream, int codecid, int flags, int64_t pts, int64_t dts, const void *data, size_t bytes)>;
@ -42,11 +46,12 @@ public:
typedef enum { decoder_ts = 0, decoder_ps } Type; typedef enum { decoder_ts = 0, decoder_ps } Type;
using Ptr = std::shared_ptr<DecoderImp>; using Ptr = std::shared_ptr<DecoderImp>;
~DecoderImp() = default; ~DecoderImp();
static Ptr createDecoder(Type type, MediaSinkInterface *sink); static Ptr createDecoder(Type type, MediaSinkInterface *sink);
ssize_t input(const uint8_t *data, size_t bytes); ssize_t input(const uint8_t *data, size_t bytes);
void flush(); void flush();
void setForceToAac(bool forceToAac);
protected: protected:
void onTrack(const Track::Ptr &track); void onTrack(const Track::Ptr &track);
@ -59,6 +64,9 @@ private:
private: private:
Decoder::Ptr _decoder; Decoder::Ptr _decoder;
#if defined(ENABLE_FFMPEG)
AudioTranscoder::Ptr _audioTranscoder;
#endif
MediaSinkInterface *_sink; MediaSinkInterface *_sink;
FrameMerger _merger{FrameMerger::none}; FrameMerger _merger{FrameMerger::none};
Track::Ptr _tracks[TrackMax]; Track::Ptr _tracks[TrackMax];