让 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_* 函数)的包含(别忘了添加对应的库):
#ifdef __cplusplus
extern "C" {
#endif
#include "libavutil/opt.h"
#include "libswresample/swresample.h"
#ifdef __cplusplus
}
#endif
然后是修改 MyStream 的定义:
struct MyStream {
AVCodecContext *CodecCtx;
int StreamIdx; struct PacketList *Packets; AVFrame *Frame; // FrameData 没什么用了,不过为了保持代码结构,还是保留下来,其作用由 FrameBuffer 代替
const uint8_t *FrameData;
const uint8_t FrameBuffer[FRAME_BUFFER_SIZE]; size_t FrameDataSize; FilePtr parent;
};
可以先定义一下 FRAME_BUFFER_SIZE:
// MP3 每一帧的大小是4608,所以如果设定成4096(一般音频可以播放)的话会造成溢出、崩溃
#define FRAME_BUFFER_SIZE (4800)
直接插入 AudioResampling() 函数(如果对这错误的时态感到别扭,改一下就好了),添加重采样支持:
static int AudioResampling(AVCodecContext * audio_dec_ctx,
AVFrame * pAudioDecodeFrame,
int out_sample_fmt,
int out_channels,
int out_sample_rate,
uint8_t* out_buf)
{
SwrContext * swr_ctx = NULL;
int data_size = ;
int ret = ;
int64_t src_ch_layout = audio_dec_ctx->channel_layout;
int64_t dst_ch_layout = AV_CH_LAYOUT_STEREO;
int dst_nb_channels = ;
int dst_linesize = ;
int src_nb_samples = ;
int dst_nb_samples = ;
int max_dst_nb_samples = ;
uint8_t **dst_data = NULL;
int resampled_data_size = ; swr_ctx = swr_alloc();
if (!swr_ctx)
{
printf("swr_alloc error \n");
return -;
} src_ch_layout = (audio_dec_ctx->channels ==
av_get_channel_layout_nb_channels(audio_dec_ctx->channel_layout)) ?
audio_dec_ctx->channel_layout :
av_get_default_channel_layout(audio_dec_ctx->channels); if (out_channels == )
{
dst_ch_layout = AV_CH_LAYOUT_MONO;
//printf("dst_ch_layout: AV_CH_LAYOUT_MONO\n");
}
else if (out_channels == )
{
dst_ch_layout = AV_CH_LAYOUT_STEREO;
//printf("dst_ch_layout: AV_CH_LAYOUT_STEREO\n");
}
else
{
dst_ch_layout = AV_CH_LAYOUT_SURROUND;
//printf("dst_ch_layout: AV_CH_LAYOUT_SURROUND\n");
} if (src_ch_layout <= )
{
printf("src_ch_layout error \n");
return -;
} src_nb_samples = pAudioDecodeFrame->nb_samples;
if (src_nb_samples <= )
{
printf("src_nb_samples error \n");
return -;
} av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, );
av_opt_set_int(swr_ctx, "in_sample_rate", audio_dec_ctx->sample_rate, );
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", audio_dec_ctx->sample_fmt, ); av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, );
av_opt_set_int(swr_ctx, "out_sample_rate", out_sample_rate, );
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", (AVSampleFormat)out_sample_fmt, ); if ((ret = swr_init(swr_ctx)) < ) {
printf("Failed to initialize the resampling context\n");
return -;
} max_dst_nb_samples = dst_nb_samples = av_rescale_rnd(src_nb_samples,
out_sample_rate, audio_dec_ctx->sample_rate, AV_ROUND_UP);
if (max_dst_nb_samples <= )
{
printf("av_rescale_rnd error \n");
return -;
} dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels,
dst_nb_samples, (AVSampleFormat)out_sample_fmt, );
if (ret < )
{
printf("av_samples_alloc_array_and_samples error \n");
return -;
} dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, audio_dec_ctx->sample_rate) +
src_nb_samples, out_sample_rate, audio_dec_ctx->sample_rate, AV_ROUND_UP);
if (dst_nb_samples <= )
{
printf("av_rescale_rnd error \n");
return -;
}
if (dst_nb_samples > max_dst_nb_samples)
{
av_free(dst_data[]);
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
dst_nb_samples, (AVSampleFormat)out_sample_fmt, );
max_dst_nb_samples = dst_nb_samples;
} if (swr_ctx)
{
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples,
(const uint8_t **)pAudioDecodeFrame->data, pAudioDecodeFrame->nb_samples);
if (ret < )
{
printf("swr_convert error \n");
return -;
} resampled_data_size = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
ret, (AVSampleFormat)out_sample_fmt, );
if (resampled_data_size < )
{
printf("av_samples_get_buffer_size error \n");
return -;
}
}
else
{
printf("swr_ctx null error \n");
return -;
} memcpy(out_buf, dst_data[], resampled_data_size); if (dst_data)
{
av_freep(&dst_data[]);
}
av_freep(&dst_data);
dst_data = NULL; if (swr_ctx)
{
swr_free(&swr_ctx);
}
return resampled_data_size;
}
修改 getAVAudioData() 函数:
uint8_t *getAVAudioData(StreamPtr stream, size_t *length)
{
int got_frame;
int len; if(length) *length = ; if(!stream || stream->CodecCtx->codec_type != AVMEDIA_TYPE_AUDIO)
return NULL; next_packet:
if(!stream->Packets && !getNextPacket(stream->parent, stream->StreamIdx))
return NULL; /* Decode some data, and check for errors */
avcodec_get_frame_defaults(stream->Frame);
while((len=avcodec_decode_audio4(stream->CodecCtx, stream->Frame,
&got_frame, &stream->Packets->pkt)) < )
{
struct PacketList *self; /* Error? Drop it and try the next, I guess... */
self = stream->Packets;
stream->Packets = self->next; av_free_packet(&self->pkt);
av_free(self); if(!stream->Packets)
goto next_packet;
} if(len < stream->Packets->pkt.size)
{
/* Move the unread data to the front and clear the end bits */
int remaining = stream->Packets->pkt.size - len;
memmove(stream->Packets->pkt.data, &stream->Packets->pkt.data[len],
remaining);
memset(&stream->Packets->pkt.data[remaining], ,
stream->Packets->pkt.size - remaining);
stream->Packets->pkt.size -= len;
}
else
{
struct PacketList *self; self = stream->Packets;
stream->Packets = self->next; av_free_packet(&self->pkt);
av_free(self);
} if(!got_frame || stream->Frame->nb_samples == )
goto next_packet; // 在这里插入重新采样代码 *length = AudioResampling(stream->CodecCtx, stream->Frame, AV_SAMPLE_FMT_S16, stream->Frame->channels, stream->Frame->sample_rate, const_cast<uint8_t *>(stream->FrameBuffer)); /* Set the output buffer size */
/*
*length = av_samples_get_buffer_size(NULL, stream->CodecCtx->channels,
stream->Frame->nb_samples,
stream->CodecCtx->sample_fmt, 1); return stream->Frame->data[0];
*/ return const_cast<uint8_t *>(stream->FrameBuffer);
}
最后是 getAVAudioInfo() 函数,我们要让它允许 planar 音频输入:
int getAVAudioInfo(StreamPtr stream, ALuint *rate, ALenum *channels, ALenum *type)
{
if(!stream || stream->CodecCtx->codec_type != AVMEDIA_TYPE_AUDIO)
return ; /* Get the sample type for OpenAL given the format detected by ffmpeg. */
if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_U8 || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P)
*type = AL_UNSIGNED_BYTE_SOFT;
else if (stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S16 || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S16P)
*type = AL_SHORT_SOFT;
else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S32 || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_S32P)
*type = AL_INT_SOFT;
else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP)
*type = AL_FLOAT_SOFT;
else if(stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_DBL || stream->CodecCtx->sample_fmt == AV_SAMPLE_FMT_DBLP)
*type = AL_DOUBLE_SOFT;
else
{
fprintf(stderr, "Unsupported ffmpeg sample format: %s\n",
av_get_sample_fmt_name(stream->CodecCtx->sample_fmt));
return ;
} /* Get the OpenAL channel configuration using the channel layout detected
* by ffmpeg. NOTE: some file types may not specify a channel layout. In
* that case, one must be guessed based on the channel count. */
if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_MONO)
*channels = AL_MONO_SOFT;
else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_STEREO)
*channels = AL_STEREO_SOFT;
else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_QUAD)
*channels = AL_QUAD_SOFT;
else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1_BACK)
*channels = AL_5POINT1_SOFT;
else if(stream->CodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1)
*channels = AL_7POINT1_SOFT;
else if(stream->CodecCtx->channel_layout == )
{
/* Unknown channel layout. Try to guess. */
if(stream->CodecCtx->channels == )
*channels = AL_MONO_SOFT;
else if(stream->CodecCtx->channels == )
*channels = AL_STEREO_SOFT;
else
{
fprintf(stderr, "Unsupported ffmpeg raw channel count: %d\n",
stream->CodecCtx->channels);
return ;
}
}
else
{
char str[];
av_get_channel_layout_string(str, sizeof(str), stream->CodecCtx->channels,
stream->CodecCtx->channel_layout);
fprintf(stderr, "Unsupported ffmpeg channel layout: %s\n", str);
return ;
} *rate = stream->CodecCtx->sample_rate; return ;
}
嗯,基本上就可以了。现在播放的话,对应的 planar 是不会显示出来的,因为显示调用的是 alhelpers.cpp 的 GetFormat(),而它是按照 OpenAL 的格式输出的。
不过这不影响播放嘛。
让 OpenAL 也支持 S16 Planar(辅以 FFmpeg)的更多相关文章
- 实战FFmpeg编译支持arm64(转)
App store要求上架的app必须支持arm64.而手中的ffmpeg还不支持arm64, 百度下ffmpeg支持arm64方法,网上有很多资料.其中一篇是使用脚本自动编译实现的.本文就是使用它的 ...
- FFMPEG列出DirectShow支持的设备
FFMPEG列出dshow支持的设备: ffmpeg -list_devices true -f dshow -idummy 举例: 采集摄像头和麦克风 ffmpeg -f dshow -i vide ...
- centos6.5 64安装ffmpeg过程支持转码mp3
百度了几个文章 大致知道了思路 首先yum源安装是木有的,只能编译安装了. 要安装ffmpeg要先安装一个yasm支持汇编优化(FFmpeg需要) 在安装一个lame,支持mp3的转码 那就是需要3步 ...
- 实战FFmpeg--编译iOS平台使用的FFmpeg库(支持arm64的FFmpeg2.6.2)
编译环境:Mac OS X 10.10.2 ,Xcode 6.3 iOS SDK 8.3 FFmpeg库的下载地址是 http://www.ffmpeg.org/releases/ . ...
- FFMpeg笔记(五) 录制小视频时几个问题解决
1. YUV数据在使用avfilter scale时在特定的分辨率下UV分量不对 由于是小视频,那么分辨率不需要太高,但是有的视频源是1080p,甚至有的是4K的,所以对视频源进行scale非常有必要 ...
- [aac @ ...] Specified sample format s16 is invalid or not supported
在使用FFmpeg打开编码器的时候出现以下错误: [aac @ 000001da19fd7200] Specified sample format s16 is invalid or not supp ...
- Linux ffmpeg命令的介绍与使用
ffmpeg使用语法 ffmpeg [[options][`-i' input_file]]... {[options] output_file}... 如果没有输入文件,那么视音频捕捉(只在Linu ...
- FFmpeg与libx264 x264接口源代码简单分析
源代码位于“libavcodec/libx264.c”中.正是有了这部分代码,使得FFmpeg可以调用libx264编码H.264视频. 从图中可以看出,libx264对应的AVCodec结构体ff ...
- ffmpeg命令学习
1.组成 程序:ffmpeg.ffplay.ffprobe.ffserverffmpeg:转码程序ffplay:播放程序ffserver:服务器程序 库:libavcodec.libavdevice. ...
随机推荐
- spring mvc + ehcache 利用注解实现缓存功能
我的spring是3.1的,因为项目需求,需要在查询时候加上缓存,小白一个,完全没有用过缓存(ehcache),摸索了一天终于会了一点通过注解来使用ehcache进行缓存,立刻给记录下来. 首先 我的 ...
- TP5与TP3.X对比
首先声明本章节并非是指导升级旧的项目到5.0,而是为了使用3.X版本的开发者更快的熟悉并上手这个全新的版本.同时也强烈建议开发者抛弃之前旧的思维模式,因为5.0是一个全新的颠覆重构版本. 需要摒弃的3 ...
- ABP理论学习之应用服务
返回总目录 本篇目录 IApplicationService接口 ApplicationService类 工作单元 数据库连接和事务管理 自动保存更改 更多 应用服务的生命周期 应用服务用于将领域逻辑 ...
- CSS3 Animation制作飘动的浮云和星星效果
带平行视差效果的星星 先看效果: 如果下方未出现效果也可前往这里查看 http://sandbox.runjs.cn/show/0lz3sl9y 下面我们利用CSS3的animation写出这样的动画 ...
- JavaScript面试时候的坑洼沟洄——数据类型
前些日子写了篇关于最近找工作的一些感受的博客 找工作的一些感悟--前端小菜的成长,没想到得到了很多园友的共鸣,得到了很多鼓励,也有园友希望我分享一些笔试.面试的经验.我觉得分享一些笔试题没太多价值,对 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (41) ------ 第七章 使用对象服务之标识关系中使用依赖实体与异步查询保存
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 7-7 标识关系中使用依赖实体 问题 你想在标识关系中插入,更新和删除一个依赖实体 ...
- AngularJs之二
今天先讲一个angularJs的表单绑定实例: <div ng-app="myApp" ng-controller="formCtrl"> < ...
- Atitit 设计模式的本质思考】
Atitit 设计模式的本质思考] 1. 世界就是有模式构建的1 1.1. 多次模式与偶然模式1 1.2. 模式就是在一种场合下对某个问题的一个解决方案."1 1.3. 模式需要三样东西. ...
- 20个JS优化代码技巧
原文网址链接为:http://www.jstips.co/ .截取了一部分本人认为比较实用的技巧分享给大家.其中一小部分技巧为JS面向对象的写法,不宜一一列出.关于JS面向对象的写法可参考本人前几篇随 ...
- Python 学习之路 (前言)
为什么要学Python 1,脚本语言本身很方便简洁,未来会有趋势 2,web 方向 3,运维方向 我是学静态语言出身的,java,毕业后从事android 应用开发,曾在工作期间学习过linux,想 ...