FFmpeg(四)音频处理OpenSL ES 使用

FFmpeg (一)基础概念入门
FFmpeg (二)视频格式和ffmpeg结构体
FFmpeg (三)自定义播放器基本知识点
FFmpeg (四)音频处理OpenSL ES 使用
FFmpeg (五)音视频同步

OpenSL ES

(Open Sound Library for Embedded Systems)

OpenSL ESTM 是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上 的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台 部署,降低执行难度,促进高级音频市场的发展。
简单来说,OpenSL ES就是嵌入式跨平台免费的音频处理库。

为什么要使用OpenSL ES

OpenGL ES能够播放与录制PCM音频。在Android SDK的Java接口中,如果需要录制PCM音频需要使用 AudioRecord ,而播放则需要使用 AudioTrack 。因此我们在C++中使用FFmpeg解码完成获取的PCM数据实际上
也是可以拷贝给Java进行播放。
但是,如果使用Java实现,数据需要从native拷贝给Java的AudioTrack播放,而AudioTrack在播放时又会需要将音 频数据从 Java 拷贝到 native 层 。如果希望减少拷贝,开发更加高效的 Android 音频应用,则建议使用 Android NDK 提供的 OpenSL ES API 接口,它支持在 native 层直接处理音频数据。这样就避免音频数据频繁在 native 层 和 Java 层拷贝,提高效率 。

OpenSL ES 的使用

OpenSL ES 通过 Object 和 Interface使用,但是这里的Object和Interface 与Java的对象与接口并不相同。

  • Object 可能会存在一个或者多个 Interface,官方为每一种 Object 都定义了一系列的 Interface;
  • Object 对象提供了各种操作,如果希望使用该对象支持的功能函数,则必须通过其 GetInterface 函数拿到 Interface 接口,然后通过 Interface 来访问功能函数 。

OpenSL ES的基本开发流程

由于OpenSL ES是Native层提供的API,在使用前须注意添加头文件和链接选项,在需要用到OpenSL ES API的源文
件中添加:

#include <SLES/OpenSLES.h>
 #include <SLES/OpenSLES_Android.h>

而在CMakeLists.txt中需要链接:OpenSLES

 target_link_libraries(
                       dnplayer
                       avfilter avformat
                         z
OpenSLES
android log )

接下来播放的流程为:

  1. 创建引擎对象
  2. 设置混音器
  3. 创建播放器
  4. 开始播放
  5. 停止播放

创建引擎对象

    /**
     *
     * 1.创建引擎
     */

    // 创建引擎engineObject
    SLObjectItf engineObject = NULL;
    SLresult result;
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 初始化
    result = (*engineObject)->Realize(engineObject,
                                      SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 获取引擎接口engineInterface
    SLEngineItf engineInterface = NULL;
    result = (*engineObject)->GetInterface(engineObject,
                                           SL_IID_ENGINE,&engineInterface);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }

设置混音器

    /**
     * 2 创建混音器
     */
    //通过引擎接口创建混音器
    SLObjectItf outputMixObject = NULL;
    result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    // 初始化混音器outputMixObject
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }

创建播放器

 
//创建buffer缓冲类型的队列作为数据定位器(获取播放数据) 2个缓冲区
    SLDataLocator_AndroidSimpleBufferQueue android_queue =
            {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    //pcm数据格式: pcm、声道数、采样率、采样位、容器大小、通道掩码(双声道)、字节序(小端)
    SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1,
                            SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
                            SL_SPEAKER_FRONT_LEFT |
                            SL_SPEAKER_FRONT_RIGHT,
                            SL_BYTEORDER_LITTLEENDIAN};

    //数据源 (数据获取器+格式)  从哪里获得播放数据
    SLDataSource slDataSource = {&android_queue, &pcm};
    
    //设置混音器
    SLDataLocator_OutputMix outputMix =
            {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&outputMix, NULL};
    //需要的接口
    const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};
    SLObjectItf bqPlayerObject = NULL;
    //创建播放器
    (*engineInterface)->CreateAudioPlayer(engineInterface, &bqPlayerObject, &slDataSource,
                                          &audioSnk, 1,
                                          ids, req);
    //初始化播放器
    (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);

这里的配置有点多,但是实际上目的就是为了获得一个播放器对象,获得对象后,需要使用播放器的功能,则需要 获取播放器对应的Interface接口。

开始播放

void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void*context) { 
    AudioChannel *audioChannel =static_cast<AudioChannel *>(context);
     //... 获得播放数据 audioChannel->buffer 与数据长度 datalen
    if (datalen > 0) {
        //加入队列并播放
        (*bq)->Enqueue(bq, audioChannel->buffer, datalen);
    }
}


    //获得播放数据队列操作接口
    SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
                                    &bqPlayerBufferQueue);
    //设置回调(启动播放器后执行回调来获取数据并播放)
    (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);

    //获取播放状态接口
    SLPlayItf bqPlayerInterface = NULL;
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerInterface);
    // 设置播放状态
    (*bqPlayerInterface)->SetPlayState(bqPlayerInterface, SL_PLAYSTATE_PLAYING);

    //需要手动调用一次播放回调
    bqPlayerCallback(bqPlayerBufferQueue, this);


停止播放

 
//设置停止状态
if (bqPlayerInterface) {
        (*bqPlayerInterface)->SetPlayState(bqPlayerInterface,SL_PLAYSTATE_STOPPED);
        bqPlayerInterface = 0;
 }
//销毁播放器
if (bqPlayerObject) {
        (*bqPlayerObject)->Destroy(bqPlayerObject);
         bqPlayerObject = 0;
        bqPlayerBufferQueue = 0;
}
//销毁混音器
if (outputMixObject) {
        (*outputMixObject)->Destroy(outputMixObject);
        outputMixObject = 0; 
}
//销毁引擎
if (engineObject) {
        (*engineObject)->Destroy(engineObject); engineObject = 0;
        engineInterface = 0;
}

知识点

OpenSL ES:
PCM: 单声道/多声道
采样率: 44.1k 21k
采样位:32位,16位

视频:RGBA,YUV420
转换 swsconvert
音频:确定一个格式

可用测试直播流

1,RTMP协议直播源
香港卫视:rtmp://live.hkstv.hk.lxdns.com/live/hks

2,RTSP协议直播源
珠海过澳门大厅摄像头监控:rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp
大熊兔(点播):rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov

3,HTTP协议直播源
香港卫视:http://live.hkstv.hk.lxdns.com/live/hks/playlist.m3u8
CCTV1高清:http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8
CCTV3高清:http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8
CCTV5高清:http://ivi.bupt.edu.cn/hls/cctv5hd.m3u8
CCTV5+高清:http://ivi.bupt.edu.cn/hls/cctv5phd.m3u8
CCTV6高清:http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8
苹果提供的测试源(点播):http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8

FFmpeg 参考
FFmpeg 编译参考
NDK 开发
FFmpeg 总结命令

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352