• 让 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 
    ");
     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
    ");
     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
    ");
     42     }
     43     else
     44     {
     45         dst_ch_layout = AV_CH_LAYOUT_SURROUND;
     46         //printf("dst_ch_layout: AV_CH_LAYOUT_SURROUND
    ");
     47     }
     48 
     49     if (src_ch_layout <= 0)
     50     {
     51         printf("src_ch_layout error 
    ");
     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 
    ");
     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
    ");
     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 
    ");
     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 
    ");
     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 
    ");
     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 
    ");
    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 
    ");
    123             return -1;
    124         }
    125     }
    126     else
    127     {
    128         printf("swr_ctx null error 
    ");
    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
    ",
    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
    ",
    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
    ", str);
    57         return 1;
    58     }
    59 
    60     *rate = stream->CodecCtx->sample_rate;
    61 
    62     return 0;
    63 }

    嗯,基本上就可以了。现在播放的话,对应的 planar 是不会显示出来的,因为显示调用的是 alhelpers.cpp 的 GetFormat(),而它是按照 OpenAL 的格式输出的。

    不过这不影响播放嘛。

  • 相关阅读:
    《Scrum实战》第4次课【全职的Scrum Master】作业汇总
    回顾Scrum学习:《Scrum实战》第4次课【全职的Scrum Master】作业
    孙式无极桩站桩要领--林泰年
    [Android Tips] 29. 如何判断当前编译的是哪个 Flavor ?
    [Jenkins] 解决 Gradle 编译包含 SVG Drawable 出现异常
    [Android Tips] 28. 如何指定运行特定的 Android Instrumentation Test
    [Gradle] 给已存在的 task 添加依赖
    [Gradle] 针对不同的项目类型应用不同的 findbugs 配置
    [Android Tips] 27. 检查 APK 是否可调试
    [Gradle] 如何强制 Gradle 重新下载项目的依赖库
  • 原文地址:https://www.cnblogs.com/GridScience/p/3647342.html
Copyright © 2020-2023  润新知