ffmpeg编程接口--音频解码
文章描述: 本文主要描述了通过ffmpeg接口对音频进行编解码的例程,及主要函数详细说明。
操作系统: Debian8
/*
* 功能描述: 打开指定音频文件,转换成PCM数据格式,并保存至文件中
* 支持多种格式文件输入mp3,m4a等
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libavcodec/avcodec.h> // 解码器
#include <libavformat/avformat.h> // 封装格式
#include <libswresample/swresample.h> // 重采样格式转换
int main(int argc, char **argv)
{
// 1.注册组件 -----------------------------------------------------
av_register_all();
// 封装格式上下文
AVFormatContext *pFormatCtx = avformat_alloc_context();
// 2.打开输入音频文件 ---------------------------------------------
if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0)
{
printf("打开输入音频文件失败.\r\n");
return -1;
}
// 3.获取音频信息 -------------------------------------------------
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
{
printf("获取音频信息失败.\r\n");
return -2;
}
// 音频解码: 找到对应的AVStream所在的pFormatCtx->streams的索引位置
int i;
int audio_stream_idx = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
// 根据类型判断是否是音频流
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
printf("查找到音频流\r\n");
audio_stream_idx = i;
}
// 根据类型判断是否是视频流
else if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
printf("查找到视频流\r\n");
}
else
{
printf("未知的编码类型: %d\r\n", pFormatCtx->streams[i]->codec->codec_type);
}
}
// 4.获取解码器 ---------------------------------------------------
// 通过stream获取解码器上下文
AVCodecContext *pCodeCtx = pFormatCtx->streams[audio_stream_idx]->codec;
// 通过编解码id获取解码器
AVCodec *pCodec = avcodec_find_decoder(pCodeCtx->codec_id);
if (pCodec == NULL)
{
printf("未找到对应解码器.\r\n");
return -3;
}
// 5.打开解码器 ---------------------------------------------------
if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0)
{
printf("解码器无法打开");
return -4;
}
// 申请数据内存:编码数据(原始数据)
AVPacket *packet = av_malloc(sizeof(AVPacket));
// 申请数据内存:解码数据
AVFrame *frame = av_frame_alloc();
// 设置解码器选项--------------------------------------------------
SwrContext *swrCtx = swr_alloc();
// 输出的采样格式 16bit PCM
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
// 输出的采样率
int out_sample_rate = 44100;
// 输出的声道布局
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO; // (立体声)
swr_alloc_set_opts(swrCtx,
out_ch_layout, // 输出的声道布局
out_sample_fmt, // 输出的采样格式 16bit PCM
out_sample_rate, // 输出的采样率
pCodeCtx->channel_layout, // 输入的声道布局
pCodeCtx->sample_fmt, // 输入的采样格式
pCodeCtx->sample_rate, // 输入的采样率
0, // 日志偏移 log_offset
NULL); // 日志指针 log_ctx
swr_init(swrCtx); // 初始化解码器
// ----------------------------------------------------------------
// 通过声道布局获取声道数
int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
int ret;
int got_frame;
// 申请单个采样点PCM数据所需要的内存(2*2*44100)
uint8_t *out_buffer = (uint8_t *)av_malloc(2 * 2 * 44100);
if (out_buffer == NULL)
{
exit(1);
}
FILE *fp_pcm = fopen("out.pcm", "wb");
// 6.一帧一帧读取压缩的音频数据AVPacket
while (av_read_frame(pFormatCtx, packet) >= 0)
{
if (packet->stream_index == audio_stream_idx)
{
// 解码:AVPacket->AVFrame
ret = avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet);
if (ret < 0)
{
printf("解码出错.\r\n");
}
// 获取有效帧
if (got_frame)
{
// 由于解码器输出的数据与PCM播放的格式不一致,所以需要转换一下
swr_convert(swrCtx,
&out_buffer, // 输出数据缓冲区
2 * 2 * 44100, // 输出缓冲区大小
(const uint8_t **)frame->data, // 输入数据缓冲区
frame->nb_samples); // 每通道的采样数(帧数)
// 通过帧数,音频格式,通道数计算每次转换后输出的字节数
int out_buffer_size = av_samples_get_buffer_size(NULL, // 传出值,与返回值相同
out_channel_nb, // 通道数
frame->nb_samples, // 采样数(帧数)
out_sample_fmt, // 音频格式
1); // 对齐模式(1 - 紧凑,不对齐)
// 转码后的数据写入文件(也可直接写入PCM进行播放)
fwrite(out_buffer, 1, out_buffer_size, fp_pcm);
}
}
av_free_packet(packet);
}
// 释放动态申请的内存
av_frame_free(&frame);
av_free(out_buffer);
swr_free(&swrCtx);
fclose(fp_pcm); // 关闭文件
avcodec_close(pCodeCtx); // 关闭解码器
avformat_close_input(&pFormatCtx); // 关闭输入文件
return 0;
}
编译选项:
-lavformat -lavcodec -lavutil -lswresample
转换后的文件测试:
由于没有文件头来指定音频格式,所以需要-f选项指定音频格式:
$ aplay -f cd out.pcm
数据结构及函数说明:
/*
* 功能描述: 打开文件,解析文件格式
* 返 回 值: 0 - 成功,负值 - 失败,发生错误
*/
int avformat_open_input(AVFormatContext **ps, // 封装句柄
const char *url, // 文件路径
AVInputFormat *fmt, // 强制指定格式,一般为NULL
AVDictionary **options); // 一般设置为NULL