add force transcoding to AAC
This commit is contained in:
parent
00e6ca3f79
commit
8ffad9c16a
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -147,9 +147,28 @@ void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t byt
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
#if defined(ENABLE_FFMPEG)
|
||||||
|
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) {
|
if (codecid != 0) {
|
||||||
WarnL << "unsupported codec type:" << getCodecName(codecid) << " " << (int)codecid;
|
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 的包
|
// 海康的 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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// 海康的 PS 流中会有 codecid 为 0xBD 的包
|
||||||
|
if (codecid != 0 && codecid != 0xBD) {
|
||||||
|
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) {
|
||||||
|
#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
|
} // namespace mediakit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue