Декодирование аудиофайла в сырые данные (decode, resampling, encode) используя FFmpeg API
Моя задача, открыть существующий аудиофайл с расширением mka (Matroska контейнер) и извлечь аудиоданные. Аудиоданные могут быть различного типа - PCM_ALAW, PCM_MULAW, S16LE, S16BE, GSM, OPUS и так далее. Также должна быть реализована логика преобразования аудиоформата из одного в другой (например, из PCM_ALAW в PCM_MULAW). Главная проблема, которую не могу решить, это так называемый RESAMPLING. Но чтобы найти решение этой проблемы, я постараюсь подробно описать логику самого проекта.
Одной из важнейших реализаций декодера является преобразование извлеченных данных из одного формата в другой. Для этого, нам нужно пройти несколько шагов:
- Открытие входящего стрима
bool AudioDecoder::openInputStream(void)
{
qint32 errorCode = -1;
if (p_frmCtx == nullptr) {
// Open the input file to read from it
if ((errorCode = avformat_open_input(&p_frmCtx,
m_settings.inputFile().toStdString().c_str(), nullptr, nullptr)) < 0) {
p_frmCtx = nullptr;
qDebug() << QString("Could not open input file: %1 (error: %2)").arg(
m_settings.inputFile()).arg(getMessageByErrorCode(errorCode));
return false;
}
return true;
}
return false;
}
- Поиск индекса аудиопотока и определения аудиокодека
bool AudioDecoder::findInputStream(void)
{
qint32 errorCode = -1;
if (p_frmCtx != nullptr) {
// Get information on the input file
if ((errorCode = avformat_find_stream_info(p_frmCtx, nullptr)) < 0) {
avformat_close_input(&p_frmCtx);
qDebug() << QString("Could not open find stream info (error: %1)").arg(getMessageByErrorCode(errorCode));
return false;
}
return true;
}
return false;
}
- Проверка всех потоков на наличие содержимого аудиопотока
bool AudioDecoder::checkInputStream(void)
{
if (p_frmCtx != nullptr) {
m_streamIndex = -1;
if (p_frmCtx->nb_streams != 1) {
for (quint32 i = 0; i < p_frmCtx->nb_streams; ++i) {
if (p_frmCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
m_streamIndex = i;
return true;
}
}
}
if (p_frmCtx->streams[0]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
m_streamIndex = 0;
return true;
}
avformat_close_input(&p_frmCtx);
}
return false;
}
- Поиск подходящего кодека для декодирования потока
bool AudioDecoder::openDecoderForInputStream(void)
{
// Find a decoder for the audio stream
AVCodec* inputCodec = nullptr;
if (!(inputCodec = avcodec_find_decoder(
(p_frmCtx)->streams[m_streamIndex]->codecpar->codec_id))) {
avformat_close_input(&p_frmCtx);
return false;
}
// Allocate a new decoding context.
p_iCdcCtx = avcodec_alloc_context3(inputCodec);
if (!p_iCdcCtx) {
avformat_close_input(&p_frmCtx);
return false;
}
// Initialize the stream parameters with demuxer information.
qint32 error = -1;
if ((error = avcodec_parameters_to_context(p_iCdcCtx,
(p_frmCtx)->streams[m_streamIndex]->codecpar)) < 0) {
avformat_close_input(&p_frmCtx);
avcodec_free_context(&p_iCdcCtx);
return false;
}
// Open the decoder for the audio stream to use it later
if ((error = avcodec_open2(p_iCdcCtx, inputCodec, nullptr)) < 0) {
avcodec_free_context(&p_iCdcCtx);
avformat_close_input(&p_frmCtx);
return false;
}
av_dump_format(p_frmCtx, 0, m_settings.inputFile().toStdString().c_str(), 0);
return true;
}
- Установка желаемого кодека для кодирования декодированных данных
bool AudioDecoder::openEncoderForOutputStream(void)
{
AVCodec* audio_avc = avcodec_find_encoder_by_name(
m_settings.audioCodec().toStdString().c_str());
if (!audio_avc) {
return false;
}
p_oCdcCtx = avcodec_alloc_context3(audio_avc);
if (!p_oCdcCtx) {
return false;
}
p_oCdcCtx->channels = m_settings.channelCount();
p_oCdcCtx->channel_layout = av_get_default_channel_layout(m_settings.channelCount());
p_oCdcCtx->sample_rate = m_settings.sampleRate();
p_oCdcCtx->sample_fmt = audio_avc->sample_fmts[0];
p_oCdcCtx->bit_rate = m_settings.constBitRate();
p_oCdcCtx->time_base.num = 1;
p_oCdcCtx->time_base.num = static_cast<int>(m_settings.sampleRate());
p_oCdcCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
if (avcodec_open2(p_oCdcCtx, audio_avc, nullptr) < 0) {
return false;
}
return true;
}
- Извлечение всех аудиоданных, декодирование, кодирование, сохранение в буфер
QByteArray AudioDecoder::getAllData(void)
{
QByteArray arr;
p_frame = av_frame_alloc();
if (!p_frame) {
return arr;
}
AVPacket p_packet;
av_init_packet(&p_packet);
int err = 0;
while ((err = av_read_frame(p_frmCtx, &p_packet)) != AVERROR_EOF) {
if (err != 0) {
break;
}
// Does the packet belong to the correct stream ?
if (p_packet.stream_index != m_streamIndex) {
// Free the buffers used by the frame and reset all fields.
av_packet_unref(&p_packet);
continue;
}
int response = avcodec_send_packet(p_iCdcCtx, &p_packet);
if (response < 0) {
return arr;
}
while (response >= 0) {
response = avcodec_receive_frame(p_iCdcCtx, p_frame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
}
else if (response < 0) {
return arr;
}
else {
AVPacket* output_packet = av_packet_alloc();
if (!output_packet) {
return arr;
}
int response = avcodec_send_frame(p_oCdcCtx, p_frame);
while (response >= 0) {
response = avcodec_receive_packet(p_oCdcCtx, output_packet);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
}
else if (response < 0) {
return arr;
}
else {
av_packet_rescale_ts(output_packet, p_oCdcCtx->time_base, p_iCdcCtx->time_base);
output_packet->stream_index = m_streamIndex;
// Save decoded - encoded audio data
for (int i = 0; i < output_packet->size; ++i) {
arr.push_back(output_packet->data[i]);
}
}
}
av_packet_unref(output_packet);
av_packet_free(&output_packet);
av_frame_unref(p_frame);
}
}
av_packet_unref(&p_packet);
}
av_packet_unref(&p_packet);
av_frame_free(&p_frame);
return arr;
}
Это и есть основные функции для извлечения аудиоданных, декодирования и кодирования в нужный нам формат.
Как было сказано ранее, моя проблема заключается в том, что я не могу правильно реализовать процесс преобразования частоты дискретизации, изменения количества каналов и формата аудиосэмплов (Resampling) перед кодированием данных и сохранением их в буфер. Я попробывал логику из данного примера, но я столкнулся с проблемой.
При тестировании, в качестве входящего файла я использовал аудиофайл со звуком stereo (2 канала). При выходе, я ему задал mono звучание (1 канал). Но аудиофайл стал воспроизводиться с большими помехами. Также, я не смог реализовать изменение частоты дискретизации. Как мне это реализовать ?
Знаю лишь одно, что использовать Resampling нужно перед кодированием в другой формат (encoder). Думаю, что этот пример имеет нужный функционал, для того, чтобы реализовать Resampling.
Прилагаю полный код проекта:
PS Игнорируется класс AudioDecoderSettings, так как это простой класс настройки декодера.
Также, стоит знать, что это всего лишь тестовая версия, и скорее всего (наверняка) есть утечки памяти и другие проблемы.
main.cpp
int main(int argc, char *argv[])
{
AudioDecoderSettings settings;
// Входной файл
settings.setInputFile("/home/test/ulaw-8000-64kbs-ch2.mka");
// На выходе, я хочу получить такую дискретизацию
settings.setSampleRate(8000);
// На выходе, я хочу получить такое количество каналов
settings.setChannelCount(1);
// На выходе, я хочу получить такой формат аудио
settings.setAudioCodec("pcm_alaw");
// На выходе, я хочу получить такой битрейт
settings.setConstBitRate(64000);
AudioDecoder decoder(settings);
// Инициализирую все зависимости библиотеки и
// открывю входящий файл для извлечения аудиоданных.
if (!decoder.init()) {
return EXIT_FAILURE;
}
// Файл, в котором будут сохранены извлеченные аудиоданные.
QFile file("/home/test/pcm_alaw.bin");
file.open(QIODevice::WriteOnly);
auto res = decoder.getAllData();
file.write(res.data(), res.size());
file.close();
return EXIT_SUCCESS;
}
audiodecoder.h
class AudioDecoder {
public:
AudioDecoder(const AudioDecoderSettings& settings);
AudioDecoder& operator=(const AudioDecoder& other) = delete;
AudioDecoder& operator=(AudioDecoder&& other) = delete;
AudioDecoder(const AudioDecoder& other) = delete;
AudioDecoder(AudioDecoder&& other) = delete;
~AudioDecoder(void);
bool init(void);
QByteArray getAllData(void);
bool term(void);
protected:
QString getMessageByErrorCode(const qint32& code);
bool openInputStream(void);
bool checkInputStream(void);
bool findInputStream(void);
bool initResampler(void);
bool openDecoderForInputStream(void);
bool openEncoderForOutputStream(void);
protected:
int m_streamIndex{ 0 };
bool m_initialized{ false };
AVFrame* p_frame = nullptr;
AVFormatContext* p_frmCtx = nullptr;
AVCodecContext* p_iCdcCtx = nullptr;
AVCodecContext* p_oCdcCtx = nullptr;
AudioDecoderSettings m_settings;
struct SwrContext* swr_ctx;
};
aduiodecoder.cpp
void printStreamInformation(const AVCodecContext* codecCtx, int audioStreamIndex)
{
fprintf(stderr, "Codec: %s\n", codecCtx->codec->long_name);
if (codecCtx->codec->sample_fmts != NULL) {
fprintf(stderr, "Supported sample formats: ");
for (int i = 0; codecCtx->codec->sample_fmts[i] != -1; ++i) {
fprintf(stderr, "%s", av_get_sample_fmt_name(codecCtx->codec->sample_fmts[i]));
if (codecCtx->codec->sample_fmts[i + 1] != -1) {
fprintf(stderr, ", ");
}
}
fprintf(stderr, "\n");
}
fprintf(stderr, "---------\n");
fprintf(stderr, "Stream: %7d\n", audioStreamIndex);
fprintf(stderr, "Sample Format: %7s\n", av_get_sample_fmt_name(codecCtx->sample_fmt));
fprintf(stderr, "Sample Rate: %7d\n", codecCtx->sample_rate);
fprintf(stderr, "Sample Size: %7d\n", av_get_bytes_per_sample(codecCtx->sample_fmt));
fprintf(stderr, "Channels: %7d\n", codecCtx->channels);
fprintf(stderr, "Planar: %7d\n", av_sample_fmt_is_planar(codecCtx->sample_fmt));
}
bool AudioDecoder::openInputStream(void)
{
qint32 errorCode = -1;
if (p_frmCtx == nullptr) {
// Open the input file to read from it
if ((errorCode = avformat_open_input(&p_frmCtx,
m_settings.inputFile().toStdString().c_str(), nullptr, nullptr)) < 0) {
p_frmCtx = nullptr;
qDebug() << QString("Could not open input file: %1 (error: %2)").arg(
m_settings.inputFile()).arg(getMessageByErrorCode(errorCode));
return false;
}
return true;
}
return false;
}
bool AudioDecoder::findInputStream(void)
{
qint32 errorCode = -1;
if (p_frmCtx != nullptr) {
// Get information on the input file
if ((errorCode = avformat_find_stream_info(p_frmCtx, nullptr)) < 0) {
avformat_close_input(&p_frmCtx);
qDebug() << QString("Could not open find stream info (error: %1)").arg(getMessageByErrorCode(errorCode));
return false;
}
return true;
}
return false;
}
bool AudioDecoder::checkInputStream(void)
{
if (p_frmCtx != nullptr) {
m_streamIndex = -1;
if (p_frmCtx->nb_streams != 1) {
for (quint32 i = 0; i < p_frmCtx->nb_streams; ++i) {
if (p_frmCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
m_streamIndex = i;
return true;
}
}
}
if (p_frmCtx->streams[0]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
m_streamIndex = 0;
return true;
}
avformat_close_input(&p_frmCtx);
}
return false;
}
bool AudioDecoder::openDecoderForInputStream(void)
{
// Find a decoder for the audio stream
AVCodec* inputCodec = nullptr;
if (!(inputCodec = avcodec_find_decoder(
(p_frmCtx)->streams[m_streamIndex]->codecpar->codec_id))) {
avformat_close_input(&p_frmCtx);
return false;
}
// Allocate a new decoding context.
p_iCdcCtx = avcodec_alloc_context3(inputCodec);
if (!p_iCdcCtx) {
avformat_close_input(&p_frmCtx);
return false;
}
// Initialize the stream parameters with demuxer information.
qint32 error = -1;
if ((error = avcodec_parameters_to_context(p_iCdcCtx,
(p_frmCtx)->streams[m_streamIndex]->codecpar)) < 0) {
avformat_close_input(&p_frmCtx);
avcodec_free_context(&p_iCdcCtx);
return false;
}
// Open the decoder for the audio stream to use it later
if ((error = avcodec_open2(p_iCdcCtx, inputCodec, nullptr)) < 0) {
avcodec_free_context(&p_iCdcCtx);
avformat_close_input(&p_frmCtx);
return false;
}
av_dump_format(p_frmCtx, 0, m_settings.inputFile().toStdString().c_str(), 0);
return true;
}
bool AudioDecoder::init(void)
{
if (m_initialized) {
qDebug() << "The decoder is already initialized";
return true;
}
if (!openInputStream()) {
return false;
}
if (!findInputStream()) {
return false;
}
if (!checkInputStream()) {
return false;
}
if (!openDecoderForInputStream()) {
return false;
}
if (!openEncoderForOutputStream()) {
return false;
}
if (!initResampler()) {
return false;
}
printStreamInformation(p_iCdcCtx, m_streamIndex);
return m_initialized = true;
}
QString AudioDecoder::getMessageByErrorCode(const qint32& code)
{
if (code < 0) {
char errorBuffer[255]{ '0' };
av_strerror(code, errorBuffer, sizeof(errorBuffer));
return QString(errorBuffer);
}
return QString();
}
AudioDecoder::AudioDecoder(const AudioDecoderSettings& settings)
: m_streamIndex(0)
, m_initialized(false)
, p_frame(nullptr)
, p_frmCtx(nullptr)
, p_iCdcCtx(nullptr)
, p_oCdcCtx(nullptr)
, m_settings(settings)
{
av_register_all();
avcodec_register_all();
}
AudioDecoder::~AudioDecoder(void)
{
term();
}
bool AudioDecoder::term(void)
{
if (!m_initialized) {
return false;
}
if (swr_ctx) {
swr_free(&swr_ctx);
}
if (p_iCdcCtx != nullptr) {
avcodec_free_context(&p_iCdcCtx);
}
if (p_oCdcCtx != nullptr) {
avcodec_free_context(&p_oCdcCtx);
}
if (p_frmCtx != nullptr) {
avformat_close_input(&p_frmCtx);
}
m_initialized = false;
return true;
}
bool AudioDecoder::openEncoderForOutputStream(void)
{
AVCodec* audio_avc = avcodec_find_encoder_by_name(
m_settings.audioCodec().toStdString().c_str());
if (!audio_avc) {
return false;
}
p_oCdcCtx = avcodec_alloc_context3(audio_avc);
if (!p_oCdcCtx) {
return false;
}
p_oCdcCtx->channels = m_settings.channelCount();
p_oCdcCtx->channel_layout = av_get_default_channel_layout(m_settings.channelCount());
p_oCdcCtx->sample_rate = m_settings.sampleRate();
p_oCdcCtx->sample_fmt = audio_avc->sample_fmts[0];
p_oCdcCtx->bit_rate = m_settings.constBitRate();
p_oCdcCtx->time_base.num = 1;
p_oCdcCtx->time_base.num = static_cast<int>(m_settings.sampleRate());
p_oCdcCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
if (avcodec_open2(p_oCdcCtx, audio_avc, nullptr) < 0) {
return false;
}
return true;
}
QByteArray AudioDecoder::getAllData(void)
{
QByteArray arr;
p_frame = av_frame_alloc();
if (!p_frame) {
return arr;
}
AVPacket p_packet;
av_init_packet(&p_packet);
int err = 0;
while ((err = av_read_frame(p_frmCtx, &p_packet)) != AVERROR_EOF) {
if (err != 0) {
break;
}
// Does the packet belong to the correct stream ?
if (p_packet.stream_index != m_streamIndex) {
// Free the buffers used by the frame and reset all fields.
av_packet_unref(&p_packet);
continue;
}
int response = avcodec_send_packet(p_iCdcCtx, &p_packet);
if (response < 0) {
return arr;
}
while (response >= 0) {
response = avcodec_receive_frame(p_iCdcCtx, p_frame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
}
else if (response < 0) {
return arr;
}
else {
AVPacket* output_packet = av_packet_alloc();
if (!output_packet) {
return arr;
}
int response = avcodec_send_frame(p_oCdcCtx, p_frame);
while (response >= 0) {
response = avcodec_receive_packet(p_oCdcCtx, output_packet);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
}
else if (response < 0) {
return arr;
}
else {
av_packet_rescale_ts(output_packet, p_oCdcCtx->time_base, p_iCdcCtx->time_base);
output_packet->stream_index = m_streamIndex;
// Save decoded - encoded audio data
for (int i = 0; i < output_packet->size; ++i) {
arr.push_back(output_packet->data[i]);
}
}
}
av_packet_unref(output_packet);
av_packet_free(&output_packet);
av_frame_unref(p_frame);
}
}
av_packet_unref(&p_packet);
}
av_packet_unref(&p_packet);
av_frame_free(&p_frame);
return arr;
}