ramlife

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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.解码/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

posted on 2020-11-18 16:18  ramlife  阅读(129)  评论(0编辑  收藏  举报