Commit 5c92b4e4 by cqm

增加FFmpegEncoder和test_audio_transcode, 主要修改有:

- 使用FFmpegAudioFifo来简化代码
- 修复转码时间戳不规则递增,导致的音频破音和不连续问题
- 使用double来保存_tsp,避免出现2.5ms的frame_size(ffmpeg内置的opus)
parent b33c227c
......@@ -63,6 +63,24 @@ private:
SwrContext *_ctx = nullptr;
};
class FFmpegAudioFifo {
public:
FFmpegAudioFifo() = default;
~FFmpegAudioFifo();
bool Write(const AVFrame *frame);
bool Read(AVFrame *frame, int sample_size);
int size() const;
private:
int _channels = 0;
int _samplerate = 0;
double _tsp = 0;
double _timebase = 0;
AVAudioFifo *_fifo = nullptr;
AVSampleFormat _format = AV_SAMPLE_FMT_NONE;
};
class TaskManager {
public:
TaskManager() = default;
......@@ -108,7 +126,7 @@ public:
bool inputFrame(const Frame::Ptr &frame, bool live, bool async, bool enable_merge = true);
void setOnDecode(onDec cb);
void flush();
const AVCodecContext *getContext() const;
const AVCodecContext *getContext() const { return _context.get(); }
private:
void onDecode(const FFmpegFrame::Ptr &frame);
......@@ -145,6 +163,41 @@ private:
AVPixelFormat _target_format = AV_PIX_FMT_NONE;
};
class FFmpegEncoder : public TaskManager, public CodecInfo {
public:
using Ptr = std::shared_ptr<FFmpegEncoder>;
using onEnc = std::function<void(const Frame::Ptr &)>;
FFmpegEncoder(const Track::Ptr &track, int thread_num = 2);
~FFmpegEncoder() override;
void flush();
CodecId getCodecId() const override { return _codecId; }
const AVCodecContext *getContext() const { return _context.get(); }
void setOnEncode(onEnc cb) { _cb = std::move(cb); }
bool inputFrame(const FFmpegFrame::Ptr &frame, bool async);
private:
bool inputFrame_l(FFmpegFrame::Ptr frame);
bool encodeFrame(AVFrame *frame);
void onEncode(AVPacket *packet);
bool openVideoCodec(int width, int height, int bitrate, const AVCodec *codec);
bool openAudioCodec(int samplerate, int channel, int bitrate, const AVCodec *codec);
private:
onEnc _cb;
CodecId _codecId;
const AVCodec *_codec = nullptr;
AVDictionary *_dict = nullptr;
std::shared_ptr<AVCodecContext> _context;
std::unique_ptr<FFmpegSws> _sws;
std::unique_ptr<FFmpegSwr> _swr;
std::unique_ptr<FFmpegAudioFifo> _fifo;
bool var_frame_size = false;
};
}//namespace mediakit
#endif// ENABLE_FFMPEG
#endif //ZLMEDIAKIT_TRANSCODE_H
......@@ -33,7 +33,14 @@ foreach(TEST_SRC ${TEST_SRC_LIST})
endif()
endif()
message(STATUS "add test: ${TEST_EXE_NAME}")
if (NOT ENABLE_FFMPEG)
# 暂时过滤掉依赖 WebRTC 的测试模块
if ("${TEST_EXE_NAME}" MATCHES "test_audio_transcode")
continue()
endif ()
endif ()
message(STATUS "add test:${TEST_EXE_NAME}")
add_executable(${TEST_EXE_NAME} ${TEST_SRC})
target_compile_options(${TEST_EXE_NAME}
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
......
#include "Codec/Transcode.h"
#include "Record/MP4Demuxer.h"
#include "Record/MP4Muxer.h"
#include "Extension/AAC.h"
#include "Extension/Opus.h"
#include "Extension/G711.h"
#include "Util/logger.h"
using namespace mediakit;
struct TransCtx {
using Ptr = std::shared_ptr<TransCtx>;
TransCtx(const char *prefix, CodecId codec) {
char path[256];
Track::Ptr track;
switch (codec) {
case CodecAAC:
track.reset(new AACTrack(44100, 1));
sprintf(path, "%s_aac.mp4", prefix);
break;
case CodecOpus:
track.reset(new OpusTrack());
sprintf(path, "%s_opus.mp4", prefix);
break;
case CodecG711A:
case CodecG711U:
track.reset(new G711Track(codec, 8000, 1, 16));
sprintf(path, "%s_711%c.mp4", prefix, codec == CodecG711A ? 'A' : 'U');
break;
default:
return;
break;
}
file.reset(new MP4Muxer());
file->openMP4(path);
file->addTrack(track);
enc.reset(new FFmpegEncoder(track));
enc->setOnEncode([this](const Frame::Ptr &frame) { file->inputFrame(frame); });
}
~TransCtx() {
enc = nullptr;
file = nullptr;
}
void inputFrame(const FFmpegFrame::Ptr &frame) { enc->inputFrame(frame, false); }
FFmpegEncoder::Ptr enc;
std::shared_ptr<MP4Muxer> file;
};
int TranscodeAudio(const char *srcPath, const char *dstPath) {
MP4Demuxer srcMp4;
srcMp4.openMP4(srcPath);
auto srcTrack = srcMp4.getTrack(TrackAudio);
if (!srcTrack) {
printf("unable to find audioTrack %s\n", srcPath);
return -1;
}
std::vector<TransCtx::Ptr> trans;
FFmpegDecoder audioDec(srcTrack);
auto dstCodec = getCodecId(dstPath);
if (!strcasecmp(dstPath, "aac"))
dstCodec = CodecAAC;
if (dstCodec != CodecInvalid) {
std::string dstFile(srcPath);
auto pos = dstFile.rfind('_');
if (pos == dstFile.npos)
pos = dstFile.rfind('.');
if (pos != dstFile.npos)
dstFile = dstFile.substr(0, pos);
if (dstCodec == srcTrack->getCodecId()) {
printf("same codec %s, skip transcode\n", dstPath);
return 0;
}
trans.push_back(std::make_shared<TransCtx>(dstFile.c_str(), dstCodec));
}
else {
for (auto codec : { CodecAAC, CodecOpus, CodecG711A, CodecG711U }) {
if (codec == srcTrack->getCodecId())
continue;
trans.push_back(std::make_shared<TransCtx>(dstPath, codec));
}
}
// srcTrack -> audioDec
srcTrack->addDelegate([&](const Frame::Ptr &frame) -> bool {
audioDec.inputFrame(frame, true, false, false);
return true;
});
// audioDec -> audioEnc
audioDec.setOnDecode([&](const FFmpegFrame::Ptr &frame) {
for (TransCtx::Ptr p : trans)
p->inputFrame(frame);
});
toolkit::Ticker tick;
printf("startReadMp4 %" PRIu64 "ms\n", srcMp4.getDurationMS());
bool key, eof;
Frame::Ptr frame;
while (true) {
// srcMp4->srcTrack
frame = srcMp4.readFrame(key, eof);
if (eof) {
printf("eof break loop, it tooks %" PRIu64 " ms\n", tick.elapsedTime());
break;
}
}
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("usage src.mp4 dst_prefix/codecName\n");
return 0;
}
toolkit::Logger::Instance().add(std::make_shared<toolkit::ConsoleChannel>());
try {
return TranscodeAudio(argv[1], argv[2]);
} catch (std::exception e) {
printf("exception: %s\n", e.what());
return -1;
}
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论