【Android FFMPEG 开发】OpenSLES 播放音频 ( 创建引擎 | 输出混音设置 | 配置输入输出 | 创建播放器 | 获取播放/队列接口 | 回调函数 | 开始播放 | 激活回调 )(二)

简介: 【Android FFMPEG 开发】OpenSLES 播放音频 ( 创建引擎 | 输出混音设置 | 配置输入输出 | 创建播放器 | 获取播放/队列接口 | 回调函数 | 开始播放 | 激活回调 )(二)

IV . OpenSL ES 播放代码 ( 详细注释 )


OpenSL ES 播放部分的代码 : 细节内容看注释吧 , 不再展开一条一条的写了 ;


// I . 创建 OpenSLES 引擎并获取引擎的接口 ( 相关代码拷贝自 Google 官方示例 native-audio )
    //      参考 : https://github.com/android/ndk-samples/blob/master/native-audio/app/src/main/cpp/native-audio-jni.c
    //声明每个方法执行的返回结果 , 一般情况下返回 SL_RESULT_SUCCESS 即执行成功
    //  该类型本质是 int 类型 , 定义的是各种类型的异常
    SLresult result;
    // 创建引擎
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    // 返回 0 成功 , 否则失败 , 一旦失败就中断退出
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // 实现引擎
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // 获取引擎接口 , 使用该接口创建输出混音器 , 音频播放器等其它对象
    //      引擎对象不提供任何调用的方法 , 引擎调用的方法都定义在接口中
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // II . 设置输出混音器
    //  输出声音 , 添加各种音效 ( 混响 , 重低音 , 环绕音 , 均衡器 等 ) , 都要通过混音器实现 ;
    // 创建输出混音器对象 , 可以指定一个混响效果参数 ( 该混淆参数可选 )
    const SLInterfaceID ids_engine[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean req_engine[1] = {SL_BOOLEAN_FALSE};
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids_engine, req_engine);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // 实现输出混音器
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // III . 获取混响接口 并 设置混响 ( 可能会失败 )
    // 获取环境混响接口
    // 如果环境混响效果不可用 , 该操作可能失败
    // either because the feature is not present, excessive CPU load, or
    // the required MODIFY_AUDIO_SETTINGS permission was not requested and granted
    result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                                               &outputMixEnvironmentalReverb);
    if (SL_RESULT_SUCCESS == result) {
        result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
                outputMixEnvironmentalReverb, &reverbSettings);
        (void)result;
    }
    //IV . 配置音源输入
    // 配置要播放的音频输入缓冲队列属性参数 , 缓冲区大小 , 音频格式 , 采样率 , 样本位数 , 通道数 , 样本大小端格式
    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    /*
        typedef struct SLDataFormat_PCM_ {
        SLuint32    formatType;     //数据格式                 SL_DATAFORMAT_PCM
        SLuint32    numChannels;    //通道数 , 左右声道 2个     2
        SLuint32    samplesPerSec;  //采样率 44100Hz           SL_SAMPLINGRATE_44_1
        SLuint32    bitsPerSample;  //采样位数 16位            SL_PCMSAMPLEFORMAT_FIXED_16
        SLuint32    containerSize;  //容器大小                 SL_PCMSAMPLEFORMAT_FIXED_16
        SLuint32    channelMask;    //通道        SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT
        SLuint32  endianness;     //小端格式                 SL_BYTEORDER_LITTLEENDIAN
    } SLDataFormat_PCM;
     */
    SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,           //PCM 格式
                                   2,                           //两个声道
                                   SL_SAMPLINGRATE_44_1,        //采样率 44100 Hz
                                   SL_PCMSAMPLEFORMAT_FIXED_16, //采样位数 16位
                                   SL_PCMSAMPLEFORMAT_FIXED_16, //容器为 16 位
                                   SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,  //左右双声道
                                   SL_BYTEORDER_LITTLEENDIAN};  //小端格式
    // 设置音频数据源 , 配置缓冲区 ( loc_bufq ) 与 音频格式 (format_pcm)
    SLDataSource audioSrc = {&loc_bufq, &format_pcm};
    // V . 配置音频输出
    // 配置混音器 : 将 outputMixObject 混音器对象装载入 SLDataLocator_OutputMix 结构体中
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    // 将 SLDataLocator_OutputMix 结构体装载到 SLDataSink 中
    //  音频输出通过 loc_outmix 输出 , 实际上是通过 outputMixObject 混音器对象输出的
    SLDataSink audioSnk = {&loc_outmix, NULL};
    // VI . 创建并实现播放器
    /*
     * 创建音频播放器:
     *      如果需要效果器时 , 不支持高性能音频
     *     ( fast audio does not support when SL_IID_EFFECTSEND is required, skip it
     *          for fast audio case )
     */
    // 操作队列接口 , 如果需要 特效接口 , 添加 SL_IID_EFFECTSEND
    const SLInterfaceID ids_player[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_EFFECTSEND,
            /*SL_IID_MUTESOLO,*/};
    const SLboolean req_player[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
            /*SL_BOOLEAN_TRUE,*/ };
    // 创建播放器
    result = (*engineEngine)->CreateAudioPlayer(
            engineEngine,
            &bqPlayerObject,
            &audioSrc, //音频输入
            &audioSnk, //音频商户处
            bqPlayerSampleRate? 2 : 3,//
            ids_player,
            req_player);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // 创建播放器对象
    result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // VII . 获取播放器接口 和 缓冲队列接口
    // 获取播放器 Player 接口 : 该接口用于设置播放器状态 , 开始 暂停 停止 播放 等操作
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // 获取播放器 缓冲队列 接口 : 该接口用于控制 音频 缓冲区数据 播放
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
                                             &bqPlayerBufferQueue);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // VIII . 注册回调函数
    // 注册缓冲区队列的回调函数 , 每次播放完数据后 , 会自动回调该函数
    //      传入参数 this , 就是 bqPlayerCallback 函数中的 context 参数
    result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // IX . 获取效果器接口 和 音量控制接口 ( 不是必须的 )
    // 获取效果器发送接口 ( get the effect send interface )
    bqPlayerEffectSend = NULL;
    if( 0 == bqPlayerSampleRate) {
        result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND,
                                                 &bqPlayerEffectSend);
        assert(SL_RESULT_SUCCESS == result);
        (void)result;
    }
#if 0   // mute/solo is not supported for sources that are known to be mono, as this is
    // get the mute/solo interface
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_MUTESOLO, &bqPlayerMuteSolo);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
#endif
    // 获取音量控制接口
    // 获取音量控制接口 ( get the volume interface ) [ 如果需要调节音量可以获取该接口 ]
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // X . 设置播放状态
    // 设置播放器正在播放状态 ( set the player's state to playing )
    result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;
    // XI. 手动调用激活回调函数
    // 手动激活 , 手动调用一次 bqPlayerCallback 回调函数
    bqPlayerCallback(bqPlayerBufferQueue, this);



IV . OpenSLES slAndroidSimpleBufferQueueCallback 回调函数声明及实现代码


1 . 回调函数原型 :


typedef void (SLAPIENTRY *slAndroidSimpleBufferQueueCallback)(

SLAndroidSimpleBufferQueueItf caller,

void *pContext

);

1

2

3

4


2 . 回调函数声明及实现 :


//每当缓冲数据播放完毕后 , 会自动回调该回调函数

// this callback handler is called every time a buffer finishes playing

void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)

{

   //获取 PCM 采样数据 , 将重采样的数据放到 data 中

   int data_size ;


//进行 FFMPEG 音频重采样 ... 大块代码参考上一篇博客


   //开始播放

   if ( data_size > 0 ){


       //通过播放器队列接口 , 将 PCM 数据加入到该队列缓冲区后 , 就会自动播放这段音频

       //  注意 , 最后一个参数是样本字节数

       (*bq)->Enqueue(bq, audioChannel->data, data_size);

   }


}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18


3 . 回调函数注册 :


   // VIII . 注册回调函数



 

// 注册缓冲区队列的回调函数 , 每次播放完数据后 , 会自动回调该函数
    //      传入参数 this , 就是 bqPlayerCallback 函数中的 context 参数
    result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
    assert(SL_RESULT_SUCCESS == result);
    (void)result;



目录
相关文章
|
3天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
1月前
|
Linux iOS开发 开发者
探索FFmpeg:实现自定义播放速度的全方位指南(一)
探索FFmpeg:实现自定义播放速度的全方位指南
90 0
|
1月前
|
编译器 开发工具 Android开发
Android 引入FFmpeg
Android 引入FFmpeg
20 0
|
2月前
|
监控 安全 Android开发
【新手必读】Airtest测试Android手机常见的设置问题
【新手必读】Airtest测试Android手机常见的设置问题
|
30天前
|
存储 缓存 编解码
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化(一)
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化
52 0
|
1月前
|
存储 编解码 调度
剖析ffmpeg视频解码播放:时间戳的处理
剖析ffmpeg视频解码播放:时间戳的处理
49 0
|
1月前
|
算法 Ubuntu API
探索FFmpeg:实现自定义播放速度的全方位指南(二)
探索FFmpeg:实现自定义播放速度的全方位指南
52 0
|
1天前
|
Android开发 内存技术
Android 通过tinyalsa调试解决录制和播放音频问题
Android 通过tinyalsa调试解决录制和播放音频问题
6 1
|
1天前
|
存储 Java Android开发
Android系统 设置第三方应用为默认Launcher实现和原理分析
Android系统 设置第三方应用为默认Launcher实现和原理分析
5 0
|
30天前
|
存储 算法 C++
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化(二)
【FFmpeg 视频播放】深入理解多媒体播放:同步策略、缓冲技术与性能优化
39 0