博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
让 OpenAL 也支持 S16 Planar(辅以 FFmpeg)
阅读量:7207 次
发布时间:2019-06-29

本文共 12169 字,大约阅读时间需要 40 分钟。

正在制作某物品,现在做到音频部分了。

原本要采用 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 __cplusplus2 extern "C" {3 #endif4 #include "libavutil/opt.h"5 #include "libswresample/swresample.h"6 #ifdef __cplusplus7 }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     else127     {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     else44     {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
(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
(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     else18     {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 detected25      * by ffmpeg. NOTE: some file types may not specify a channel layout. In26      * 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         else45         {46             fprintf(stderr, "Unsupported ffmpeg raw channel count: %d\n",47                     stream->CodecCtx->channels);48             return 1;49         }50     }51     else52     {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 的格式输出的。

不过这不影响播放嘛。

转载于:https://www.cnblogs.com/GridScience/p/3647342.html

你可能感兴趣的文章
Swift 中的 @autoclosure
查看>>
多迪将企业的Python工程师定位成哪几类?
查看>>
Rom 检测
查看>>
【iOS工具】rvm、Ruby环境和CocoaPods安装使用及相关报错问题解决(2016 12 15 更新)...
查看>>
Weex学习指南
查看>>
TiDB DevCon 2019 报名开启:年度最高规格的 TiDB 技术大会
查看>>
React Native 初体验
查看>>
数据结构与算法 | 线性表 —— 链表
查看>>
Python3 websocket通信
查看>>
使用MarkDown画矩阵
查看>>
JavaScript函数式编程学习
查看>>
ESXi6.7安装流程和bug处理
查看>>
Alibaba Cluster Data 开放下载:270GB 数据揭秘你不知道的阿里巴巴数据中心
查看>>
巧用这19条MySQL优化,效率至少提高3倍
查看>>
【译】Swift算法俱乐部-查找最大/最小值
查看>>
跟着老司机玩转Node自定义命令行
查看>>
react-redux的Provider和connect
查看>>
杂七杂八的前端基础01——函数作用域
查看>>
new操作符具体干了啥
查看>>
iOS响应链
查看>>