【FFmpeg(2016)】SwrContext 转换PCM音频位数
来源:互联网 发布:好的健身软件 编辑:程序博客网 时间:2024/04/29 16:34
【相关博客】
【FFmpeg(2016)】PCM编码AAC
【FFmpeg(2016)】SwrContext重采样结构体
【前言】
这两天在做一些音频的编码,但FFmpeg的编码库avcodec有20M这么大,所以决定使用其他库进行编码。网上发现faac体积小,直接编解码,于是决定使用faac库作为编码模块。
但是从faac的源码发现,它只支持如下格式的PCM编码:
PCM Sample Input Format
0 FAAC_INPUT_NULLinvalid, signifies a misconfigured config
1 FAAC_INPUT_16BITnative endian 16bit
2 FAAC_INPUT_24BITnative endian 24bit in 24 bits (not implemented)
3 FAAC_INPUT_32BITnative endian 24bit in 32 bits (DEFAULT)
4 FAAC_INPUT_FLOAT32bit floating point
我要编码的PCM 是32bit float的,使用faac编码时却总是出错,于是决定使用ffmpeg的转码库进行位数转换。
【FFmpeg及音频相关概念】
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};这是ffmpeg罗列的音频格式,带P的表示是 平面数据。
在介绍音频存储格式的两个概念: 交错立体声存储 和 双单声道。
交错立体声存储,即对于两声道的音频存储格式是:L(一个采样点)RLRLRLR.....
双单声道,即是:L(一个采样点)LLLLLLLLLLLLRRRRRRRRRRRRLLLLLLLLLLLLLLLRRRRRRRRRRRRRR。。。
目前为止,FFmpeg API中,我发现只有sample_fmt为 AV_SAMPLE_FMT_FLTP 的音频编码器能打开,也就说它只支持编码格式为AV_SAMPLE_FMT_FLTP的PCM音频。
它虽然并不支持其他格式PCM数据的编码,但是它支持所有格式的相互转换,所以不管你拥有什么格式的音频,只要使用SwrContext转换到AV_SAMPLE_FMT_FLTP,即可使用FFmpeg编码。
【代码】
原始PCM数据:
sample_rate = 48000
channels = 2
sample_fmt = AV_SAMPLE_FMT_FLT
目标PCM数据:
sample_rate = 48000
channels = 2
sample_fmt = AV_SAMPLE_FMT_U8
原始数据 及 目标数据 均是交错立体声存储(必须注意:FFmpeg PCM格式枚举中带P的后缀表明用此参数初始化的编码器,源数据必须是交错立体声存储)
SwrContext *swr_ctx = NULL; FILE *fp_in = NULL;fp_in = fopen("in.pcm", "rb"); FILE *outpcm = NULL;outpcm = fopen("out.pcm", "wb");uint8_t **convert_data;uint8_t* frame_buf; int framenum = 100000; int nb_samples = 1024; int channels = 2; int samplerate = 48000;swr_ctx = swr_alloc_set_opts(NULL,AV_CH_LAYOUT_STEREO,AV_SAMPLE_FMT_U8,samplerate,AV_CH_LAYOUT_STEREO,AV_SAMPLE_FMT_FLT,samplerate,0, NULL);swr_init(swr_ctx);/* 分配空间 ,存储结果*/convert_data = (uint8_t**)calloc( channels,sizeof(*convert_data));int sizes = av_samples_alloc( convert_data, NULL,channels, nb_samples,AV_SAMPLE_FMT_U8, 0); /* 存储原始数据 */size = av_samples_get_buffer_size(NULL, channels, nb_samples, AV_SAMPLE_FMT_FLT, 0);frame_buf = (uint8_t *)av_malloc(size);ret = avcodec_fill_audio_frame(pFrame, channels, AV_SAMPLE_FMT_FLT, (const uint8_t*)frame_buf, size, 0);if (ret < 0){qDebug() << "avcodec_fill_audio_frame error ";return 0;}for (i = 0; i < framenum; i++) {av_init_packet(&pkt);pkt.data = NULL; // packet data will be allocated by the encoderpkt.size = 0;//Read raw dataif (fread(frame_buf, 1, size, fp_in) <= 0) {printf("Failed to read raw data! \n");return -1;}else if (feof(fp_in)) {break;}/* 转换数据,令各自声道的音频数据存储在不同的数组(分别由不同指针指向)*/swr_convert(swr_ctx, convert_data, nb_samples ,(const uint8_t**)pFrame->data, nb_samples );fwrite(convert_data[0], nb_samples * channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_U8), 1, outpcm); }
【代码解析】
FFmpeg 在处理 交错立体声存储 以及 双声单声道 中结构体的存储方式稍有不同。
由于是由AV_SAMPLE_FMT_FLT 转 AV_SAMPLE_FMT_U8,那么目标数据的内存空间只需要源数据的1/4.
下面先看 av_sample_alloc 发生什么事,
int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align){ uint8_t *buf; //计算总共内存大小 int size = av_samples_get_buffer_size(NULL, nb_channels, nb_samples, sample_fmt, align); if (size < 0) return size; buf = av_malloc(size); if (!buf) return AVERROR(ENOMEM); size = av_samples_fill_arrays(audio_data, linesize, buf, nb_channels, nb_samples, sample_fmt, align); if (size < 0) { av_free(buf); return size; } //省略... return size;}
int av_samples_fill_arrays(uint8_t **audio_data, int *linesize, const uint8_t *buf, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align){ int ch, planar, buf_size, line_size; planar = av_sample_fmt_is_planar(sample_fmt); buf_size = av_samples_get_buffer_size(&line_size, nb_channels, nb_samples, sample_fmt, align); if (buf_size < 0) return buf_size; audio_data[0] = (uint8_t *)buf; // 这里对平面数据做多一些操作 for (ch = 1; planar && ch < nb_channels; ch++) audio_data[ch] = audio_data[ch-1] + line_size; if (linesize) *linesize = line_size; return buf_size;}
以上调用av_samples_get_buffer_size的第一个参数返回每个声道所占的字节数。
可以发现,如果是此PCM属于平面数据,那么内存将平均分配给每一个声道,此时有如audio_data[0] audio_data[1]....;
对于非平面数据,那么只需要将第一个元素audio_data[0]指向首指针
int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align){ int line_size; int sample_size = av_get_bytes_per_sample(sample_fmt); int planar = av_sample_fmt_is_planar(sample_fmt); /* validate parameter ranges */ if (!sample_size || nb_samples <= 0 || nb_channels <= 0) return AVERROR(EINVAL); /* auto-select alignment if not specified */ if (!align) { if (nb_samples > INT_MAX - 31) return AVERROR(EINVAL); align = 1; nb_samples = FFALIGN(nb_samples, 32); } /* check for integer overflow */ if (nb_channels > INT_MAX / align || (int64_t)nb_channels * nb_samples > (INT_MAX - (align * nb_channels)) / sample_size) return AVERROR(EINVAL); line_size = planar ? FFALIGN(nb_samples * sample_size, align) : // 这里根据是否是平面数据 FFALIGN(nb_samples * sample_size * nb_channels, align); if (linesize) *linesize = line_size; return planar ? line_size * nb_channels : line_size;}
由于我们使用的是AV_SAMPLES_FMT_U8,所以处理结果数据时只需用 convert_data[0] 指针。
avcodec_fill_audio_frame 调用同理,由于是AV_SAMPLES_FMT_FLT非平面格式,pFrame->data[0]指向所有数据(左右声道)
其实它们的内存空间一样大,区别就是
非planr : data[0] -> [8192字节]
planr: data[0]->[4096字节] data[1]->[4096字节]
【对于平面与非平面格式的相互转换】
平面数据中,有多少声道,那么data二维指针中就有多少元素指向内存,打个比方,如果是三声道
那么AVFrame中的 data[0] data[1] data[2] 分别指向响应位置,以提供内存给每个声道(顺便说下,每个声道的数据大小是一样的)
如下是FFmpeg支持的layout
/** * @} * @defgroup channel_mask_c Audio channel layouts * @{ * */#define AV_CH_LAYOUT_MONO (AV_CH_FRONT_CENTER)#define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)#define AV_CH_LAYOUT_2POINT1 (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_2_1 (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_SURROUND (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)#define AV_CH_LAYOUT_3POINT1 (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_4POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_4POINT1 (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_2_2 (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)#define AV_CH_LAYOUT_QUAD (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_5POINT0 (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)#define AV_CH_LAYOUT_5POINT1 (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_5POINT0_BACK (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_5POINT1_BACK (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_6POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_6POINT0_FRONT (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)#define AV_CH_LAYOUT_HEXAGONAL (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_6POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_6POINT1_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_6POINT1_FRONT (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_7POINT0 (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_7POINT0_FRONT (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)#define AV_CH_LAYOUT_7POINT1 (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_7POINT1_WIDE (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)#define AV_CH_LAYOUT_OCTAGONAL (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_HEXADECAGONAL (AV_CH_LAYOUT_OCTAGONAL|AV_CH_WIDE_LEFT|AV_CH_WIDE_RIGHT|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_FRONT_CENTER|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT)#define AV_CH_LAYOUT_STEREO_DOWNMIX (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT)
- 【FFmpeg(2016)】SwrContext 转换PCM音频位数
- 【FFmpeg(2016)】SwrContext重采样结构体
- ffmpeg提取音频存为PCM
- ffmpeg添加MP4的pcm音频支持
- ffmpeg解码音频保存为PCM
- 【ffmpeg】 音频转换命令
- DirectShow捕获音频之PCM的转换
- FFMPEG学习----分离视音频里的PCM数据
- 【FFmpeg杂记】音频解码输出PCM格式数据分析
- 【FFmpeg(2016)】PCM编码AAC
- PCM音频
- PCM音频
- PCM音频
- 关于PCM音频数据的相关转换算法
- 关于PCM音频数据的相关转换算法
- ffmpeg 指令 PCM 转换 WAV & AAC & AMR & MP3
- ffmpeg提取音频、转换为mp3
- 开源音频转换方案FFmpeg
- vb隐藏进程
- 学Java能干什么?
- linux下防火墙iptables原理及使用
- 跟我一起写 Makefile(二)
- 九度OJ 1061
- 【FFmpeg(2016)】SwrContext 转换PCM音频位数
- C++高级进阶
- Leetcode 1——Two Sum
- 软件更新通知
- 跟我一起写 Makefile(三)
- const 的称谓:常量?
- Oracle Database 12.1中rman show all的新变化
- 第二篇
- WIN32,GetBitmapBits与GetPixel