ffmpeg 学习笔记
1. 解码 mp3 的时候,使用 ffprobe,当 ffmpeg 的版本不同,出来的结果也不一样。
在 早期版本的时候,显示 sample format 是 s16 在 3.X 版本的时候,显示是 S16P 在 4.X 版本的时候,显示是 FLTP
经过实际测试,这个只是在解码的时候,写入 PCM 的格式。而且和 ffmpeg 本身的 build 相关。 当我们在代码中循环打印 支持的 格式的时候:
printf("support formats: %d \n", *(codec->sample_fmts + i));
这个 codec 对应的是 AVCodec,它的 sample_fmts 对应的是 const enum AVSampleFormat *sample_fmts; AVSampleFormat 的 定义是在 libavutil/samplefmt.h 中。
enum AVSampleFormat {
AV_SAMPLE_FMT_NONE = -1,
AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
AV_SAMPLE_FMT_S16, ///< signed 16 bits
AV_SAMPLE_FMT_S32, ///< signed 32 bits
AV_SAMPLE_FMT_FLT, ///< float
AV_SAMPLE_FMT_DBL, ///< double
AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
AV_SAMPLE_FMT_FLTP, ///< float, planar
AV_SAMPLE_FMT_DBLP, ///< double, planar
AV_SAMPLE_FMT_S64, ///< signed 64 bits
AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar
AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically
};
AVCodec 的定义在 libavcodec/avcodec.h 中。 codecpar 是在 AVStream 中定义的,AVCodecParameters *codecpar; AVStream 是在 libavformat/avformat.h 中。 发现 3.X 版本支持的是 1 和 6,对应到 S16 和 S16P, 而 4.X 版本支持的是 3 和 8.对应到 FLT 和 FLTP。
所以使用 3.X 版本解压出来的 PCM 的播放指令是:
play -t raw -r 48k -e signed-integer -b 16 -c 2 test.pcm
使用 4.X 版本解压出来的 PCM 的播放指令是:
play -t raw -r 48k -e floating-point -b 32 -c 2 ./data_decode/out.pcm
参考: https://trac.ffmpeg.org/ticket/7321 https://stackoverflow.com/questions/35226255/audio-sample-format-s16p-ffmpeg-or-audio-codec-bug https://bbs.csdn.net/topics/391984409
2. ffprobe 的用法可以参考:
https://my.oschina.net/u/4324861/blog/4325767 https://blog.csdn.net/byc6352/article/details/96729348 https://www.cnblogs.com/renhui/p/9209664.html
3. mp3 的 文件格式 和 编码,参考:
https://www.cnblogs.com/ranson7zop/p/7655474.html https://blog.csdn.net/xiahouzuoxin/article/details/7849249
4. API 变更记录
https://blog.csdn.net/leixiaohua1020/article/details/41013567
5. 使用 lame 编码 mp3 ,可以参考:
https://www.jianshu.com/p/dce4e2e9ed75 https://blog.csdn.net/bjrxyz/article/details/73435407 https://blog.csdn.net/rrrfff/article/details/18701885?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param https://blog.csdn.net/gonner_2011/article/details/77183947?utm_medium=distribute.pc_relevant.none-task-blog-title-2&spm=1001.2101.3001.4242 https://blog.csdn.net/jody1989/article/details/75642579 https://blog.csdn.net/ssllkkyyaa/article/details/90400302
6. 编码 mp3 除了 lame 还可以考虑 libshine,参考:
http://zhgeaits.me/android/2016/06/17/android-ffmpeg.html
7. 旧版本的 avcodec_decode_audio4 被废弃了,需要用收发机制。
旧版本的写法:
const char * infile = IN_FILE;
const char * outfile = OUT_FILE;
//注册所有容器解码器
av_register_all();
//printf("the video file is %s\n",argv[1]);
AVFormatContext * fmt_ctx = avformat_alloc_context();
//打开文件
if (avformat_open_input(&fmt_ctx, infile , NULL, NULL) < 0) {
printf("open file error");
return -1;
}
//读取音频格式文件信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
printf("find stream info error");
return -1;
}
// 打印出解析到的媒体信息
av_dump_format(fmt_ctx, 0, infile, 0);
//获取音频索引
int audio_stream_index = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_stream_index = i;
printf("find audio stream index\n");
break;
}
}
if (audio_stream_index == -1) {
printf("did not find a audio stream\n");
return -1;
}
//获取解码器
AVCodecContext *codec_ctx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[audio_stream_index]->codecpar);
AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id);
if (codec == NULL) {
printf("unsupported codec !\n");
return -1;
}
//打开解码器
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
printf("could not open codec");
return -1;
}
//分配AVPacket和AVFrame内存,用于接收音频数据,解码数据
AVPacket *packet = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
//接收解码结果
int got_frame;
int index = 0;
//pcm输出文件
FILE *out_file = fopen(outfile, "wb");
//将音频数据读入packet
while (av_read_frame(fmt_ctx, packet) == 0) {
//取音频索引packet
if (packet->stream_index == audio_stream_index) {
//将packet解码成AVFrame
if (avcodec_decode_audio4(codec_ctx, frame, &got_frame, packet) < 0) {
printf("decode error:%d", index);
break;
}
if (got_frame > 0) {
//printf("decode frame:%d", index++);
//想将单个声道pcm数据写入文件
fwrite(frame->data[0], 1, static_cast<size_t>(frame->linesize[0]), out_file);
}
}
}
printf("decode finish...");
//释放资源
av_packet_unref(packet);
av_frame_free(&frame);
avcodec_close(codec_ctx);
avformat_close_input(&fmt_ctx);
fclose(out_file);
}
新版本的解码这样写:
void decode(const char * infile, const char * outfile)
{
AVFormatContext * fmt_ctx = 0; // ffmpeg的全局上下文,所有ffmpeg操作都需要
AVCodecContext * codec_ctx = 0; // ffmpeg编码上下文
AVCodec * codec = 0; // ffmpeg编码器
AVPacket * packet = 0; // ffmpag单帧数据包
AVFrame * frame = 0; // ffmpeg单帧缓存
FILE * out_file = NULL; // 用于文件操作
int audio_stream_index = -1; // 音频序号
//注册所有容器解码器
av_register_all();
fmt_ctx = avformat_alloc_context();
if (fmt_ctx == NULL) {
printf("failed to alloc av format context\n");
goto END;
}
//打开文件
if (avformat_open_input(&fmt_ctx, infile , NULL, NULL) < 0) {
printf("open file error");
goto END;
}
//读取音频格式文件信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
printf("find stream info error");
goto END;
}
// 打印出解析到的媒体信息
av_dump_format(fmt_ctx, 0, infile, 0);
//获取音频索引
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_stream_index = i;
printf("find audio stream index\n");
break;
}
}
if (audio_stream_index == -1) {
printf("did not find a audio stream\n");
goto END;
}
//获取解码器
codec_ctx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[audio_stream_index]->codecpar);
codec = avcodec_find_decoder(codec_ctx->codec_id);
if (codec == NULL) {
printf("unsupported codec !\n");
goto END;
}
//打开解码器
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
printf("could not open codec");
goto END;
}
printf("codec name: %s, channels: %d, sample rate: %d, sample format %d\n", codec->name, codec_ctx->channels, codec_ctx->sample_rate, codec_ctx->sample_fmt);
//分配AVPacket和AVFrame内存,用于接收音频数据,解码数据
packet = av_packet_alloc();
frame = av_frame_alloc();
if (!packet || !frame) {
printf("failed to alloc packet or frame\n");
goto END;
}
//pcm输出文件
out_file = fopen(outfile, "wb");
//将音频数据读入packet
while (av_read_frame(fmt_ctx, packet) == 0) {
//取音频索引packet
if (packet->stream_index == audio_stream_index) {
int ret = 0;
// 将封装包发往解码器
if ((ret = avcodec_send_packet(codec_ctx, packet))) {
printf("failed to avcodec_send_packet, ret = %d\n", ret);
break;
}
// 从解码器循环拿取数据帧
while (!avcodec_receive_frame(codec_ctx, frame)) {
// 获取每个通道每次采样占用几个字节, S16P格式是2字节
int bytes_num = av_get_bytes_per_sample(codec_ctx->sample_fmt);
for (int index = 0; index < frame->nb_samples; index++) {
// 交错的方式写入
for (int channel = 0; channel < codec_ctx->channels; channel++) {
fwrite((char *)frame->data[channel] + bytes_num * index, 1, bytes_num, out_file);
}
}
av_packet_unref(packet);
}
}
}
printf("decode finish...\n");
//释放资源
END:
fclose(out_file);
if (frame) {
av_frame_free(&frame);
printf("free frame.\n");
}
if (packet) {
av_packet_unref(packet);
printf("free packet.\n");
}
if (codec_ctx) {
avcodec_close(codec_ctx);
printf("free codec context.\n");
}
if (fmt_ctx) {
avformat_close_input(&fmt_ctx);
printf("free format context.\n");
}
}
编码这样写:
int encode(const char * infile, const char *outfile, uint32_t sample_rate, uint8_t channel_num)
{
// 输入输出文件指针
FILE * in_file = NULL;
FILE * out_file = NULL;
// 打开输入文件
if ((in_file = fopen(infile, "rb+")) == NULL) {
printf("failed to open %s \n", infile);
return -1;
}
// 打开输出文件
if ((out_file = fopen(outfile, "wb+")) == NULL) {
printf("failed to open %s \n", outfile);
fclose(in_file);
return -1;
}
// 初始化编码参数
lame_t lame = lame_init();
// 设置编码参数
lame_set_in_samplerate(lame, sample_rate);
lame_set_VBR(lame, vbr_default);
lame_set_num_channels(lame, channel_num);
// 初始化编码器
lame_init_params(lame);
// 编码时用来存放数据的数组,大小建议为 mp3 的采样率 * 1.25 + 7200
// PCM 通常使用16bit 数据,占用两个字节,如果是双通道,那么读取PCM交错数据时一次最好是 2 * 2 = 4 个字节。
uint32_t size = (sample_rate * 1.25) / (2 * channel_num) * (2 * channel_num) + 7200;
// 申请存放数据内存, 如果申请出错,需要释放占用的资源
int16_t * pcm_buffer = NULL;
uint8_t * mp3_buffer = NULL;
pcm_buffer = (int16_t *)malloc(size);
mp3_buffer = (uint8_t *)malloc(size);
if (!pcm_buffer || !mp3_buffer) {
if (pcm_buffer)
free(pcm_buffer);
if (mp3_buffer)
free(mp3_buffer);
lame_close(lame);
fclose(in_file);
fclose(out_file);
printf("buffer malloc error!\n");
return -1;
}
printf("encode start...\n");
// 读取 PCM 的字节数目
size_t read_num = 0;
do {
// 读取 PCM 数据
read_num = fread(pcm_buffer, 1, size, in_file);
// 转换 MP3 数据,如果获得的数目是0,说明转换结束,需要把 lame 转换剩余的数据全部存放到 MP3 数组里面。
int write_num = 0;
if (read_num == 0) {
write_num = lame_encode_flush(lame, mp3_buffer, size);
} else {
write_num = lame_encode_buffer_interleaved(lame, pcm_buffer, static_cast<int>(read_num / sizeof(int16_t) / channel_num), mp3_buffer, size);
}
// 转换后的数据写入文件。
fwrite(mp3_buffer, write_num, 1, out_file);
} while (read_num > 0);
printf("encode finish\n");
// 给文件添加 MP3 的 TAG 信息。
lame_mp3_tags_fid(lame, out_file);
// 释放资源
lame_close(lame);
fclose(in_file);
fclose(out_file);
return 0;
}
参考: https://blog.csdn.net/weixin_41353840/article/details/108000466
8. 解码 pcm 当中容易碰到的坑和相应的编码格式,可以参考:
https://blog.csdn.net/qq21497936/article/details/108799279 https://blog.csdn.net/leixiaohua1020/article/details/50534316
9. 雷神关于编码和解码相关的文章:
https://blog.csdn.net/leixiaohua1020/article/details/50534316 https://blog.csdn.net/leixiaohua1020/article/details/42181571 https://blog.csdn.net/leixiaohua1020/article/details/8652605 https://blog.csdn.net/leixiaohua1020/article/details/15811977 https://blog.csdn.net/leixiaohua1020/article/details/50534316 https://blog.csdn.net/leixiaohua1020/article/list/3
10. ffmpeg 读取码率和帧信息的可以参考:
https://blog.51cto.com/ticktick/1869849 https://blog.51cto.com/ticktick/1872008 https://blog.51cto.com/ticktick/1867059
11. ffmpeg 的例程可以参考:
https://stackoverflow.com/questions/2641460/ffmpeg-c-api-documentation-tutorial
12. av_register_all() 这个函数废弃了。
参考:https://github.com/leandromoreira/ffmpeg-libav-tutorial/issues/29
13. 测试的 mp3 可以从这个网站下载:
http://www.goodkejian.com/erge.htm
14. 命令行形式使用 ffmpeg
参考: https://segmentfault.com/a/1190000016652277 https://cloud.tencent.com/developer/article/1566587
15. 解码例程可以参考:
https://github.com/iamyours/FFmpegAudioPlayer https://gitee.com/zouwm1995/ffmpeg-demo/blob/master/demo/2.%E8%A7%A3%E7%A0%81/decode.c https://www.jianshu.com/p/8ff162ac55bd https://blog.csdn.net/weixin_44721044/article/details/104736782 https://bbs.csdn.net/topics/390401066
16. 编译 ffmpeg
https://www.cnblogs.com/CoderTian/p/6655568.html
17. S16 变成了 S16P,参考:
https://blog.csdn.net/chinabinlang/article/details/47616257 https://blog.csdn.net/disadministrator/article/details/43734335
18. 查看当前的 ffmpeg 支持哪些编码解码器,类似于这样。
ffmpeg -codecs | grep mp3
19. 用于 ffmpeg 的 cmake 可以参考:
https://www.cnblogs.com/liuxia19872003/archive/2012/11/09/2763173.html