diff --git a/src/Codec/Transcode.cpp b/src/Codec/Transcode.cpp index 25f9e77c..ec282cb3 100644 --- a/src/Codec/Transcode.cpp +++ b/src/Codec/Transcode.cpp @@ -9,14 +9,9 @@ */ #if defined(ENABLE_FFMPEG) -#if !defined(_WIN32) -#include -#endif -#include "Util/File.h" -#include "Util/uv_errno.h" #include "Transcode.h" +#include "FFmpeg/Utils.h" #include "Extension/AAC.h" -#include "Common/config.h" #define MAX_DELAY_SECOND 3 using namespace std; @@ -24,91 +19,7 @@ using namespace toolkit; 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 alloc_av_packet() { - auto pkt = std::shared_ptr(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 &code } FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std::vector &codec_name) { - setupFFmpeg(); + ffmpeg::setupFFmpeg(); const AVCodec *codec = nullptr; const AVCodec *codec_default = nullptr; 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) { break; } - if (checkIfSupportedNvidia()) { + if (ffmpeg::checkIfSupportedNvidia()) { codec = getCodec({{"libopenh264"}, {AV_CODEC_ID_H264}, {"h264_qsv"}, {"h264_videotoolbox"}, {"h264_cuvid"}, {"h264_nvmpi"}}); } else { 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) { break; } - if (checkIfSupportedNvidia()) { + if (ffmpeg::checkIfSupportedNvidia()) { codec = getCodec({{AV_CODEC_ID_HEVC}, {"hevc_qsv"}, {"hevc_videotoolbox"}, {"hevc_cuvid"}, {"hevc_nvmpi"}}); } else { 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) { //硬件编解码器打开失败,尝试软件的 - WarnL << "打开解码器" << codec->name << "失败,原因是:" << ffmpeg_err(ret) << ", 再尝试打开解码器" << codec_default->name; + WarnL << "打开解码器" << codec->name << "失败,原因是:" << ffmpeg::ffmpeg_err(ret) << ", 再尝试打开解码器" << codec_default->name; codec = codec_default; 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; } if (ret < 0) { - WarnL << "avcodec_receive_frame failed:" << ffmpeg_err(ret); + WarnL << "avcodec_receive_frame failed:" << ffmpeg::ffmpeg_err(ret); break; } 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) { TimeTicker2(30, TraceL); - auto pkt = alloc_av_packet(); + auto pkt = ffmpeg::alloc_av_packet(); pkt->data = (uint8_t *) data; pkt->size = size; 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()); if (ret < 0) { if (ret != AVERROR_INVALIDDATA) { - WarnL << "avcodec_send_packet failed:" << ffmpeg_err(ret); + WarnL << "avcodec_send_packet failed:" << ffmpeg::ffmpeg_err(ret); } return false; } @@ -550,7 +461,7 @@ bool FFmpegDecoder::decodeFrame(const char *data, size_t size, uint64_t dts, uin break; } if (ret < 0) { - WarnL << "avcodec_receive_frame failed:" << ffmpeg_err(ret); + WarnL << "avcodec_receive_frame failed:" << ffmpeg::ffmpeg_err(ret); break; } 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; 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 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))) { - WarnL << "sws_scale failed:" << ffmpeg_err(ret); + WarnL << "sws_scale failed:" << ffmpeg::ffmpeg_err(ret); return nullptr; } diff --git a/src/Common/config.cpp b/src/Common/config.cpp index df9f7c5d..bd2634c8 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -356,6 +356,7 @@ const string kBenchmarkMode = "benchmark_mode"; const string kWaitTrackReady = "wait_track_ready"; const string kPlayTrack = "play_track"; const string kProxyUrl = "proxy_url"; +const string kForceToAac = "force_to_aac"; } // namespace Client } // namespace mediakit diff --git a/src/Common/config.h b/src/Common/config.h index 68726225..59572584 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -379,34 +379,54 @@ extern const std::string kGopCache; * rtsp/rtmp播放器、推流器相关设置名, * 这些设置项都不是配置文件用 * 只用于设置某个播放器或推流器实例用 + * rtsp/rtmp Player/Pusher instance config + * these config not for ini file + * just for PlayerBase/PusherBase instance */ namespace Client { // 指定网卡ip +// Setting network card ip extern const std::string kNetAdapter; // 设置rtp传输类型,可选项有0(tcp,默认)、1(udp)、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; // rtsp认证用户名 +// rtsp authentication username extern const std::string kRtspUser; // 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; // rtsp认证用用户密码是否为md5类型 +// Whether the user password used for RTSP authentication is MD5 type extern const std::string kRtspPwdIsMD5; // 握手超时时间,默认10,000 毫秒 +// Handshake timeout time, default 10,000 milliseconds extern const std::string kTimeoutMS; // rtp/rtmp包接收超时时间,默认5000秒 +// rtp/rtmp packet receive timeout time, default 5000 seconds extern const std::string kMediaTimeoutMS; // rtsp/rtmp心跳时间,默认5000毫秒 +// rtsp/rtmp heartbeat time, default 5000 milliseconds extern const std::string kBeatIntervalMS; // 是否为性能测试模式,性能测试模式开启后不会解析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; // 播放器在触发播放成功事件时,是否等待所有track ready时再回调 +// Should the player wait for all tracks to be ready before triggering the playback success event callback? extern const std::string kWaitTrackReady; // rtsp播放指定track,可选项有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; //设置代理url,目前只支持http协议 +// Setting proxy url, currently only http protocol is supported extern const std::string kProxyUrl; +//强制转码为AAC +// Force transcoding to AAC +extern const std::string kForceToAac; } // namespace Client } // namespace mediakit diff --git a/src/FFmpeg/AudioTranscoder.cpp b/src/FFmpeg/AudioTranscoder.cpp new file mode 100644 index 00000000..f9f763d7 --- /dev/null +++ b/src/FFmpeg/AudioTranscoder.cpp @@ -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(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> 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) \ No newline at end of file diff --git a/src/FFmpeg/AudioTranscoder.h b/src/FFmpeg/AudioTranscoder.h new file mode 100644 index 00000000..11c39413 --- /dev/null +++ b/src/FFmpeg/AudioTranscoder.h @@ -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; + using onTranscodeCallback = std::function; + + AudioTranscoder() { + _decoder_ctx = std::unique_ptr>(nullptr, [](AVCodecContext *p) { + if (p) { + avcodec_free_context(&p); + } + }); + _encoder_ctx = std::unique_ptr>(nullptr, [](AVCodecContext *p) { + if (p) { + avcodec_free_context(&p); + } + }); + _swr_ctx = std::unique_ptr>(nullptr, [](SwrContext *p) { + if (p) { + swr_free(&p); + } + }); + _fifo = std::unique_ptr>(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> _decoder_ctx; + std::unique_ptr> _encoder_ctx; + std::unique_ptr> _swr_ctx; + std::unique_ptr> _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 diff --git a/src/FFmpeg/Utils.cpp b/src/FFmpeg/Utils.cpp new file mode 100644 index 00000000..0dce35d7 --- /dev/null +++ b/src/FFmpeg/Utils.cpp @@ -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 +#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 alloc_av_packet() { + auto pkt = std::shared_ptr(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) \ No newline at end of file diff --git a/src/FFmpeg/Utils.h b/src/FFmpeg/Utils.h new file mode 100644 index 00000000..c2caa1db --- /dev/null +++ b/src/FFmpeg/Utils.h @@ -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 +#include +#include +#include +#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 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 diff --git a/src/Http/HlsPlayer.cpp b/src/Http/HlsPlayer.cpp index 6d15a45c..8ffb876f 100644 --- a/src/Http/HlsPlayer.cpp +++ b/src/Http/HlsPlayer.cpp @@ -433,6 +433,8 @@ HlsPlayerImp::HlsPlayerImp(const EventPoller::Ptr &poller) : PlayerImpsetForceToAac((*this)[Client::kForceToAac].as()); + } if (_decoder && _demuxer) { @@ -450,6 +452,7 @@ void HlsPlayerImp::onPlayResult(const SockException &ex) { PlayerImp::onPlayResult(ex); } else { auto demuxer = std::make_shared(); + demuxer->mINI::operator=(*this); demuxer->start(getPoller(), this); _demuxer = std::move(demuxer); } diff --git a/src/Http/HlsPlayer.h b/src/Http/HlsPlayer.h index 82cd2696..de21aab8 100644 --- a/src/Http/HlsPlayer.h +++ b/src/Http/HlsPlayer.h @@ -23,7 +23,7 @@ namespace mediakit { -class HlsDemuxer : public MediaSinkInterface , public TrackSource, public std::enable_shared_from_this { +class HlsDemuxer : public MediaSinkInterface , public TrackSource, public toolkit::mINI, public std::enable_shared_from_this { public: HlsDemuxer() = default; ~HlsDemuxer() override { _timer = nullptr; } diff --git a/src/Rtp/Decoder.cpp b/src/Rtp/Decoder.cpp index 37a347ac..930981bd 100644 --- a/src/Rtp/Decoder.cpp +++ b/src/Rtp/Decoder.cpp @@ -147,9 +147,28 @@ void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t byt } default: - if(codecid != 0){ - WarnL<< "unsupported codec type:" << getCodecName(codecid) << " " << (int)codecid; +#if defined(ENABLE_FFMPEG) + if (_audioTranscoder && ffmpeg::is_audio_psi_codec(codecid)) { + if (_audioTranscoder->initDecodeCodec(codecid)) { + onTrack(std::make_shared()); + } 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; } @@ -221,10 +240,24 @@ void DecoderImp::onDecode(int stream,int codecid,int flags,int64_t pts,int64_t d } default: +#if defined(ENABLE_FFMPEG) + if (_audioTranscoder && ffmpeg::is_audio_psi_codec(codecid)) { + if (!_tracks[TrackAudio]) { + onTrack(std::make_shared()); + } + _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 的包 if (codecid != 0 && codecid != 0xBD) { - WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int) codecid; + WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int)codecid; } +#endif break; } } @@ -244,6 +277,27 @@ void DecoderImp::onTrack(const Track::Ptr &track) { void DecoderImp::onFrame(const Frame::Ptr &frame) { _sink->inputFrame(frame); } - -}//namespace mediakit +void DecoderImp::setForceToAac(bool forceToAac) { +#if defined(ENABLE_FFMPEG) + if (forceToAac) { + _audioTranscoder = std::make_shared(); + _audioTranscoder->setOnOutputAudioData([this](const uint8_t *data, int size, int64_t pts, int64_t dts) { + if (!_tracks[TrackAudio]) { + onTrack(std::make_shared()); + } + onFrame(std::make_shared(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 diff --git a/src/Rtp/Decoder.h b/src/Rtp/Decoder.h index c6f8aacf..39b97714 100644 --- a/src/Rtp/Decoder.h +++ b/src/Rtp/Decoder.h @@ -15,10 +15,14 @@ #include #include #include "Common/MediaSink.h" - +#include "FFmpeg/AudioTranscoder.h" +#include "Util/mini.h" +#if defined(ENABLE_FFMPEG) +#include "FFmpeg/AudioTranscoder.h" +#endif namespace mediakit { -class Decoder { +class Decoder: public toolkit::mINI{ public: using Ptr = std::shared_ptr; using onDecode = std::function; @@ -42,11 +46,12 @@ public: typedef enum { decoder_ts = 0, decoder_ps } Type; using Ptr = std::shared_ptr; - ~DecoderImp() = default; + ~DecoderImp(); static Ptr createDecoder(Type type, MediaSinkInterface *sink); ssize_t input(const uint8_t *data, size_t bytes); void flush(); + void setForceToAac(bool forceToAac); protected: void onTrack(const Track::Ptr &track); @@ -59,6 +64,9 @@ private: private: Decoder::Ptr _decoder; +#if defined(ENABLE_FFMPEG) + AudioTranscoder::Ptr _audioTranscoder; +#endif MediaSinkInterface *_sink; FrameMerger _merger{FrameMerger::none}; Track::Ptr _tracks[TrackMax];