1. 介绍
在提取音视频文件中音频的PCM
数据时,使用avcodec_receive_frame()
函数进行解码时,遇到了一些问题,代码在Visual Studio 2022
中运行结果符合预期,但是在CLion
中运行时,获取的AVFrame
有错误,和VS
中获得的结果不一样。
FFMpeg 5.1.2
2. 源码
Utils.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
extern "C" {
#include <libavutil/error.h>
}
static char* wrap_av_err2str(int errnum) {
static char str[256] = {0};
return av_make_error_string(str, sizeof(str), errnum);
}
AudioDecoder2.h
#pragma once
extern "C"
{
#include "libavutil/log.h"
#include "libavutil/imgutils.h"
#include "libavutil/samplefmt.h"
#include "libavutil/timestamp.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#include "Utils.h"
#include <cinttypes>
class AudioDecoder2
{
public:
AudioDecoder2();
AudioDecoder2(const char* src_filename, const char* dst_filename);
~AudioDecoder2();
int start();
private:
int ret;
char src_filename[256];
char dst_filename[256];
FILE* dst_fd = NULL;
AVFormatContext* ifmt_ctx = NULL;
AVCodecContext* audio_dec_ctx = NULL;
const AVCodec* audio_dec = NULL;
AVStream* audio_stream = NULL;
int audio_stream_index = -1;
AVFrame* frame = NULL;
AVPacket* packet = NULL;
};
AudioDecoder2.cpp
#include "AudioDecoder2.h"
AudioDecoder2::AudioDecoder2(const char* src_filename, const char* dst_filename) {
sscanf(src_filename, "%s", this->src_filename);
sscanf(dst_filename, "%s", this->dst_filename);
}
int AudioDecoder2::start() {
// 设置日志输出级别
av_log_set_level(AV_LOG_INFO);
// 打开输入文件
ret = avformat_open_input(&ifmt_ctx, src_filename, NULL, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Can't open source file:%s\n", wrap_av_err2str(ret));
return -1;
}
// 读取一部分数据获得一些相关信息
ret = avformat_find_stream_info(ifmt_ctx, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to find stream information: %s\n", wrap_av_err2str(ret));
return -1;
}
// 查找流
audio_stream_index = -1;
audio_stream_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_stream_index < 0)
{
av_log(NULL, AV_LOG_DEBUG, "Failed to find the best audio stream!\n");
return AVERROR(EINVAL);
}
// 获取流
audio_stream = ifmt_ctx->streams[audio_stream_index];
// 通过数据流查找对应的解码器
audio_dec = avcodec_find_decoder(audio_stream->codecpar->codec_id);
if (audio_dec == NULL)
{
av_log(NULL, AV_LOG_ERROR, "Failed to find codec.\n");
return AVERROR(EINVAL);
}
// 创建解码器上下文
audio_dec_ctx = avcodec_alloc_context3(audio_dec);
if (audio_dec_ctx == NULL)
{
av_log(NULL, AV_LOG_ERROR, "Failed to allocate the codec context.\n");
return AVERROR(ENOMEM);
}
// 从输入流中拷贝对应的参数到解码器中
ret = avcodec_parameters_to_context(audio_dec_ctx, audio_stream->codecpar);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to copy codec parameters to decoder context.\n");
return ret;
}
// 打开解码器
ret = avcodec_open2(audio_dec_ctx, audio_dec, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to open codec.\n");
return ret;
}
// 创建输出文件
dst_fd = fopen(dst_filename, "wb");
if (!dst_fd)
{
av_log(NULL, AV_LOG_ERROR, "Could not open destination file %s\n", dst_filename);
return -1;
}
// 分配帧
frame = av_frame_alloc();
if (frame == NULL)
{
av_log(NULL, AV_LOG_ERROR, "Failed to allocate frame.\n");
return AVERROR(ENOMEM);
}
// 分配数据包
packet = av_packet_alloc();
if (packet == NULL)
{
av_log(NULL, AV_LOG_ERROR, "Failed to allocate packet.\n");
return AVERROR(ENOMEM);
}
// 读取音频流的数据包,并解码
while (av_read_frame(ifmt_ctx, packet) >= 0)
{
if (packet->stream_index == audio_stream_index)
{
ret = avcodec_send_packet(audio_dec_ctx, packet);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to decode packet.\n");
return ret;
}
// 对数据进行解码输出
while (ret >= 0)
{
ret = avcodec_receive_frame(audio_dec_ctx, frame);
if (ret < 0)
{
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
{
break;
}
else
{
break;
}
}
if (ret == 0)
{
// 将内容输出到文件
av_log(NULL, AV_LOG_INFO, "pts:%10" PRId64"\t packet size:%d\n",
packet->pts, packet->size);
size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample((enum AVSampleFormat)frame->format);
fwrite(frame->extended_data[0], 1, unpadded_linesize, dst_fd);
}
av_frame_unref(frame);
}
}
av_packet_unref(packet);
}
// 刷新解码器
while (true)
{
if (!(audio_dec->capabilities & AV_CODEC_CAP_DELAY))
{
return 0;
}
ret = avcodec_send_packet(audio_dec_ctx, packet);
if (ret < 0)
{
// av_log(NULL, AV_LOG_ERROR, "Failed to decode packet.\n");
// return ret;
break;
}
// 对数据进行解码输出
while (ret >= 0)
{
ret = avcodec_receive_frame(audio_dec_ctx, frame);
if (ret < 0)
{
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
{
break;
}
}
// 将内容输出到文件
size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample((enum AVSampleFormat)frame->format);
fwrite(frame->extended_data[0], 1, unpadded_linesize, dst_fd);
av_frame_unref(frame);
}
av_packet_unref(packet);
}
// 释放资源
avcodec_free_context(&audio_dec_ctx);
avformat_close_input(&ifmt_ctx);
if (dst_fd)
{
fclose(dst_fd);
}
av_packet_free(&packet);
av_frame_free(&frame);
return 0;
}
main.cpp
#include <iostream>
#include "AudioDecoder2.h"
using namespace std;
int main(int argc, char** argv)
{
char src_filename[20] = "ball_10s.mp4";
char audio_dst_filename[20] = "ball_10s.pcm";
AudioDecoder2* audioDecoder2 = new AudioDecoder2(src_filename, audio_dst_filename);
audioDecoder2->start();
return 0;
}
3. 问题
Visual Studio 2022
调试结果
CLion
中调试结果