让 OpenAL 也支持 S16 Planar(辅以 FFmpeg)
正在制作某物品,现在做到音频部分了。
原本要采用 SDL2_mixer 的,不过实验结果表明其失真非常严重,还带有大量的电噪声。不知道是不是我打开的方式不对……
一气之下去看 OpenAL,结果吃了闭门羹(维护中,只有 mailing list 和 specification)。转投 FMOD,不过又考虑到其授权方式,还是放弃了。最终回到 OpenAL。使用的是 OpenAL-Soft。
OpenAL 呢,好的方面是开源+授权,坏的方面……呃,至少在刚刚的测试中,代码维护甚至没有 SDL 好。直接编译 .c 示例失败,耍小聪明改成 .cpp 拿去编译才成功。
在接下来的代码中,需要用到 OpenAL-Soft(1.15.1)和 FFmpeg。
看 OpenAL-Soft 自带的示例 alstream.c。为了方便起见,接下来的 C 源代码文件全部改成 C++ 源代码文件去……同时不要忘了在 FFmpeg 的头文件上下加 extern "C"!(为什么他们不考虑这一点?)
好,编译示例,运行。(注意,各种 dependencies 这里就不提了。)随便选择一个含有音频的、可以被 FFmpeg 解码的文件。
不对啊!很有可能出现以下错误信息:
Opened "OpenAL Soft"
AL_SOFT_buffer_samples supported!
Unsupported ffmpeg sample format: s16p
Error getting audio info for 01.mpg
Done.
这是……怎么回事?经过测试,SDL_mixer 可以播放同一个文件,不过正如之前所说的,失真&噪声。看其采样格式:S16P(Signed 16-bit, Planar←平面?)。再看源代码,S16(Signed 16-bit)是支持的。(当然,如果强制将那几个 if 修改一下的话,你会听到神奇的东西……)S16 和 S16P 的不同点是在于数据的排列方式,前者是相邻连续排列,后者是分离排列。但是现在有相当多的音频文件采用 planar 的方案,不仅是 S16,U8、S32、F32、F64 都有对应的 planar 方式。现在,目标就是:让这个示例支持 planar。
思路很简单。我的上一篇随笔中,有一个 AudioResampling() 函数,这里直接拿来用吧!(秉持拿来主义!鲁迅先生不谢。)
接下来就是好戏了。
又试验了一下,播放 U8/Mono 的时候出现崩溃,不知道原因。调试的时候内存是越界的。
先是添加对 libswresample 和 libavutil(要用到 opt_* 函数)的包含(别忘了添加对应的库):
1 #ifdef __cplusplus 2 extern "C" { 3 #endif 4 #include "libavutil/opt.h" 5 #include "libswresample/swresample.h" 6 #ifdef __cplusplus 7 } 8 #endif
然后是修改 MyStream 的定义:
1 struct MyStream { 2 AVCodecContext *CodecCtx; 3 int StreamIdx; 4 5 struct PacketList *Packets; 6 7 AVFrame *Frame; 8 9 // FrameData 没什么用了,不过为了保持代码结构,还是保留下来,其作用由 FrameBuffer 代替 10 const uint8_t *FrameData; 11 const uint8_t FrameBuffer[FRAME_BUFFER_SIZE]; 12 13 size_t FrameDataSize; 14 15 FilePtr parent; 16 };
可以先定义一下 FRAME_BUFFER_SIZE:
1 // MP3 每一帧的大小是4608,所以如果设定成4096(一般音频可以播放)的话会造成溢出、崩溃 2 #define FRAME_BUFFER_SIZE (4800)
直接插入 AudioResampling() 函数(如果对这错误的时态感到别扭,改一下就好了),添加重采样支持:
1 static int AudioResampling(AVCodecContext * audio_dec_ctx, 2 AVFrame * pAudioDecodeFrame, 3 int out_sample_fmt, 4 int out_channels, 5 int out_sample_rate, 6 uint8_t* out_buf) 7 { 8 SwrContext * swr_ctx = NULL; 9 int data_size = 0; 10 int ret = 0; 11 int64_t src_ch_layout = audio_dec_ctx->channel_layout; 12 int64_t dst_ch_layout = AV_CH_LAYOUT_STEREO; 13 int dst_nb_channels = 0; 14 int dst_linesize = 0; 15 int src_nb_samples = 0; 16 int dst_nb_samples = 0; 17 int max_dst_nb_samples = 0; 18 uint8_t **dst_data = NULL; 19 int resampled_data_size = 0; 20 21 swr_ctx = swr_alloc(); 22 if (!swr_ctx) 23 { 24 printf("swr_alloc error \n"); 25 return -1; 26 } 27 28 src_ch_layout = (audio_dec_ctx->channels == 29 av_get_channel_layout_nb_channels(audio_dec_ctx->channel_layout)) ? 30 audio_dec_ctx->channel_layout : 31 av_get_default_channel_layout(audio_dec_ctx->channels); 32 33 if (out_channels == 1) 34 { 35 dst_ch_layout = AV_CH_LAYOUT_MONO; 36 //printf("dst_ch_layout: AV_CH_LAYOUT_MONO\n"); 37 } 38 else if (out_channels == 2) 39 { 40 dst_ch_layout = AV_CH_LAYOUT_STEREO; 41 //printf("dst_ch_layout: AV_CH_LAYOUT_STEREO\n"); 42 } 43 else 44 { 45 dst_ch_layout = AV_CH_LAYOUT_SURROUND; 46 //printf("dst_ch_layout: AV_CH_LAYOUT_SURROUND\n"); 47 } 48 49 if (src_ch_layout <= 0) 50 { 51 printf("src_ch_layout error \n"); 52 return -1; 53 } 54 55 src_nb_samples = pAudioDecodeFrame->nb_samples; 56 if (src_nb_samples <= 0) 57 { 58 printf("src_nb_samples error \n"); 59 return -1; 60 } 61 62 av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0); 63 av_opt_set_int(swr_ctx, "in_sample_rate", audio_dec_ctx->sample_rate, 0); 64 av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", audio_dec_ctx->sample_fmt, 0); 65 66 av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0); 67 av_opt_set_int(swr_ctx, "out_sample_rate", out_sample_rate, 0); 68 av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", (AVSampleFormat)out_sample_fmt, 0); 69 70 if ((ret = swr_init(swr_ctx)) < 0) { 71 printf("Failed to initialize the resampling context\n"); 72 return -1; 73 } 74 75 max_dst_nb_samples = dst_nb_samples = av_rescale_rnd(src_nb_samples, 76 out_sample_rate, audio_dec_ctx->sample_rate, AV_ROUND_UP); 77 if (max_dst_nb_samples <= 0) 78 { 79 printf("av_rescale_rnd error \n"); 80 return -1; 81 } 82 83 dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout); 84 ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels, 85 dst_nb_samples, (AVSampleFormat)out_sample_fmt, 0); 86 if (ret < 0) 87 { 88 printf("av_samples_alloc_array_and_samples error \n"); 89 return -1; 90 } 91 92 93 dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, audio_dec_ctx->sample_rate) + 94 src_nb_samples, out_sample_rate, audio_dec_ctx->sample_rate, AV_ROUND_UP); 95 if (dst_nb_samples <= 0) 96 { 97 printf("av_rescale_rnd error \n"); 98 return -1; 99 } 100 if (dst_nb_samples > max_dst_nb_samples) 101 { 102 av_free(dst_data[0]); 103 ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels, 104 dst_nb_samples, (AVSampleFormat)out_sample_fmt, 1); 105 max_dst_nb_samples = dst_nb_samples; 106 } 107 108 if (swr_ctx) 109 { 110 ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, 111 (const uint8_t **)pAudioDecodeFrame->data, pAudioDecodeFrame->nb_samples); 112 if (ret < 0) 113 { 114 printf("swr_convert error \n"); 115 return -1; 116 } 117 118 resampled_data_size = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels, 119 ret, (AVSampleFormat)out_sample_fmt, 1); 120 if (resampled_data_size < 0) 121 { 122 printf("av_samples_get_buffer_size error \n"); 123 return -1; 124 } 125 } 126 else 127 { 128 printf("swr_ctx null error \n"); 129 return -1; 130 } 131 132 memcpy(out_buf, dst_data[0], resampled_data_size); 133 134 if (dst_data) 135 { 136 av_freep(&dst_data[0]); 137 } 138 av_freep(&dst_data); 139 dst_data = NULL; 140 141 if (swr_ctx) 142 { 143 swr_free(&swr_ctx); 144 } 145 return resampled_data_size; 146 }
修改 getAVAudioData() 函数:
1 uint8_t *getAVAudioData(StreamPtr stream, size_t *length) 2 { 3 int got_frame; 4 int len; 5 6 if(length) *length = 0; 7 8 if(!stream || stream->CodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) 9 return NULL; 10 11 next_packet: 12 if(!stream->Packets && !getNextPacket(stream->parent, stream->StreamIdx)) 13 return NULL; 14 15 /* Decode some data, and check for errors */ 16 avcodec_get_frame_defaults(stream->Frame); 17 while((len=avcodec_decode_audio4(stream->CodecCtx, stream->Frame, 18 &got_frame, &stream->Packets->pkt)) < 0) 19 { 20 struct PacketList *self; 21 22 /* Error? Drop it and try the next, I guess... */ 23 self = stream->Packets; 24 stream->Packets = self->next; 25 26 av_free_packet(&self->pkt); 27 av_free(self); 28 29 if(!stream->Packets) 30 goto next_packet; 31 } 32 33 if(len < stream->Packets->pkt.size) 34 { 35 /* Move the unread data to the front and clear the end bits */ 36 int remaining = stream->Packets->pkt.size - len; 37 memmove(stream->Packets->pkt.data, &stream->Packets->pkt.data[len], 38 remaining); 39 memset(&stream->Packets->pkt.data[remaining], 0, 40 stream->Packets->pkt.size - remaining); 41 stream->Packets->pkt.size -= len; 42 } 43 else 44 { 45 struct PacketList *self; 46 47 self = stream->Packets; 48 stream->Packets = self->next; 49 50 av_free_packet(&self->pkt); 51 av_free(self); 52 } 53 54 if(!got_frame || stream->Frame->nb_samples == 0) 55 goto next_packet; 56 57 58 // 在这里插入重新采样代码 59 60 *length = AudioResampling(stream->CodecCtx, stream->Frame, AV_SAMPLE_FMT_S16, stream->Frame->channels, stream->Frame->sample_rate, const_cast<uint8_t *>(stream->FrameBuffer)); 61 62 /* Set the output buffer size */ 63 /* 64 *length = av_samples_get_buffer_size(NULL, stream->CodecCtx->channels, 65 stream->Frame->nb_samples, 66 stream->CodecCtx->sample_fmt, 1); 67 68 return stream->Frame->data[0]; 69 */ 70 71 return const_cast<uint8_t *>(stream->FrameBuffer); 72 }
最后是 getAVAudioInfo() 函数,我们要让它允许 planar 音频输入:
1 int getAVAudioInfo(StreamPtr stream, ALuint *rate, ALenum *channels, ALenum *type) 2 { 3 if(!stream || stream->CodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) 4 return 1; 5 6 /* Get the sample type for OpenAL given the format detected by ffmpeg. */ 7 if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_U8 || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) 8 *type = AL_UNSIGNED_BYTE_SOFT; 9 else if (stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S16 || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S16P) 10 *type = AL_SHORT_SOFT; 11 else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S32 || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S32P) 12 *type = AL_INT_SOFT; 13 else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) 14 *type = AL_FLOAT_SOFT; 15 else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_DBL || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_DBLP) 16 *type = AL_DOUBLE_SOFT; 17 else 18 { 19 fprintf(stderr, "Unsupported ffmpeg sample format: %s\n", 20 av_get_sample_fmt_name(stream->CodecCtx->sample_fmt)); 21 return 1; 22 } 23 24 /* Get the OpenAL channel configuration using the channel layout detected 25 * by ffmpeg. NOTE: some file types may not specify a channel layout. In 26 * that case, one must be guessed based on the channel count. */ 27 if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_MONO) 28 *channels = AL_MONO_SOFT; 29 else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_STEREO) 30 *channels = AL_STEREO_SOFT; 31 else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_QUAD) 32 *channels = AL_QUAD_SOFT; 33 else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK) 34 *channels = AL_5POINT1_SOFT; 35 else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1) 36 *channels = AL_7POINT1_SOFT; 37 else if(stream->CodecCtx->channel_layout == 0) 38 { 39 /* Unknown channel layout. Try to guess. */ 40 if(stream->CodecCtx->channels == 1) 41 *channels = AL_MONO_SOFT; 42 else if(stream->CodecCtx->channels == 2) 43 *channels = AL_STEREO_SOFT; 44 else 45 { 46 fprintf(stderr, "Unsupported ffmpeg raw channel count: %d\n", 47 stream->CodecCtx->channels); 48 return 1; 49 } 50 } 51 else 52 { 53 char str[1024]; 54 av_get_channel_layout_string(str, sizeof(str), stream->CodecCtx->channels, 55 stream->CodecCtx->channel_layout); 56 fprintf(stderr, "Unsupported ffmpeg channel layout: %s\n", str); 57 return 1; 58 } 59 60 *rate = stream->CodecCtx->sample_rate; 61 62 return 0; 63 }
嗯,基本上就可以了。现在播放的话,对应的 planar 是不会显示出来的,因为显示调用的是 alhelpers.cpp 的 GetFormat(),而它是按照 OpenAL 的格式输出的。
不过这不影响播放嘛。