ffmpeg 解碼音訊(aac、mp3)輸出pcm檔案
阿新 • • 發佈:2021-01-07
ffmpeg 解碼音訊(aac、mp3)輸出pcm檔案
播放pcm可以參考: ffplay -ar 48000 -ac 2 -f f32le out.pcm
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
#define AUDIO_INBUF_SIZE 10240
#define AUDIO_REFILL_THRESH 4096
static void decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame, FILE *outfile)
{
int i, ch;
int ret, sample_size;
int send_ret = 1;
do
{
//傳入要解碼的packet
ret = avcodec_send_packet(dec_ctx, pkt);
//AVERROR(EAGAIN) 傳入失敗,表示先要receive frame再重新send packet
if(ret == AVERROR(EAGAIN))
{
send_ret = 0;
fprintf(stderr, "avcodec_send_packet = AVERROR(EAGAIN)\n");
}
else if(ret < 0)
{
char err[128] = {0};
av_strerror(ret, err, 128);
fprintf(stderr, "avcodec_send_packet = ret < 0 : %s\n" , err);
return;
}
while (ret >= 0)
{
//呼叫avcodec_receive_frame會在內部首先呼叫av_frame_unref來釋放frame本來的資料
//就是這次呼叫會將上次呼叫返回的frame資料釋放
ret = avcodec_receive_frame(dec_ctx, frame);
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if(ret < 0)
{
fprintf(stderr, "avcodec_receive_frame = ret < 0\n");
exit(1);
}
//獲取取樣點佔用的位元組
sample_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
if(sample_size < 0)
{
fprintf(stderr, "av_get_bytes_per_sample = sample_size < 0\n");
exit(1);
}
//在第一幀的時候輸出一下音訊資訊
static int print_info = 1;
if(print_info)
{
print_info = 0;
printf("ar-samplerate: %uHz\n", frame->sample_rate);
printf("ac-channel: %u\n", frame->channels);
printf("f-format: %u\n", frame->format);
}
//平面方式:
//LLLLRRRRLLLLRRRRLLLLRRRR (LLLLRRRR這樣為一個音訊幀)
//交錯方式:
//LRLRLRLRLRLRLRLRLR (LR這樣為一個音訊樣本)
//按交錯方式寫入(nb_samples這一幀多少樣本)
for(i = 0; i < frame->nb_samples; i++)
{ //dec_ctx->channels 多少個通道的資料
for(int channle = 0; channle < dec_ctx->channels; channle++)
{
//frame->data[0] = L 通道
//frame->data[1] = R 通道
fwrite(frame->data[channle] + sample_size * i, 1, sample_size, outfile);
}
}
}
}while(!send_ret);
}
#define AAC 0
int main()
{
const char* outfilename = "out.pcm";
#if AAC
const char* filename = "believe.aac";
#else
const char* filename = "believe.mp3";
#endif
const AVCodec *codec;
AVCodecContext *codec_ctx= NULL;
AVCodecParserContext *parser = NULL;
int len = 0;
int ret = 0;
FILE *infile = NULL;
FILE *outfile = NULL;
uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t *data = NULL;
size_t data_size = 0;
AVPacket *pkt = NULL;
AVFrame *de_frame = NULL;
//申請AVPacket本身的記憶體
pkt = av_packet_alloc();
//要使用的解碼器ID
enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;
if(strstr(filename, "aac") != NULL)
{
audio_codec_id = AV_CODEC_ID_AAC;
}
else if(strstr(filename, ".mp3") != NULL)
{
audio_codec_id = AV_CODEC_ID_MP3;
}
else
{
printf("audio_codec_id = AV_CODEC_ID_NONE\n");
return 0;
}
//查詢相應的解碼器
codec = avcodec_find_decoder(audio_codec_id);
if(!codec)
{
printf("Codec not find!\n");
return 0;
}
//根據解碼器ID獲取裸流的解析器
parser = av_parser_init(codec->id);
if(!parser)
{
printf("Parser not find!\n");
return 0;
}
//分配codec使用的上下文
codec_ctx = avcodec_alloc_context3(codec);
if(!codec_ctx)
{
printf("avcodec_alloc_context3 failed!\n");
return 0;
}
//將解碼器和解碼使用的上下文關聯
if(avcodec_open2(codec_ctx, codec, NULL) < 0)
{
printf("avcodec_open2 failed!\n");
return 0;
}
//開啟輸入檔案
infile = fopen(filename, "rb");
if(!infile)
{
printf("infile fopen failed!\n");
return 0;
}
//輸出檔案
outfile = fopen(outfilename, "wb");
if(!outfile)
{
printf("outfilie fopen failed!\n");
return 0;
}
int file_end = 0;
//讀取檔案開始解碼
data = inbuf;
data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);
de_frame = av_frame_alloc();
while (data_size > 0)
{
//獲取傳入到avcodec_send_packet一個packet的資料量
//(一幀的量可能也會多幀的量,這裡測試是一幀的量)
ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
data, data_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if(ret < 0)
{
printf("av_parser_parser2 Error!\n");
return 0;
}
//使用了多少資料做一個偏移
data += ret;
data_size -= ret;
if(pkt->size)
decode(codec_ctx, pkt, de_frame, outfile);
//如果當前緩衝區中資料少於AUDIO_REFILL_THRESH就再讀
//避免多次讀檔案
if((data_size < AUDIO_REFILL_THRESH) && !file_end )
{
//剩餘資料移動緩衝區前
memmove(inbuf, data, data_size);
data = inbuf;
//跨過已有資料儲存
len = fread(data + data_size, 1, AUDIO_INBUF_SIZE - data_size, infile);
if(len > 0)
data_size += len;
else if(len == 0)
{
file_end = 1;
printf("file end!\n");
}
}
}
//沖刷解碼器
pkt->data = NULL;
pkt->size = 0;
decode(codec_ctx, pkt, de_frame, outfile);
fclose(infile);
fclose(outfile);
avcodec_free_context(&codec_ctx);
av_parser_close(parser);
av_frame_free(&de_frame);
av_packet_free(&pkt);
printf("audio decoder end!\n");
return 0;
}