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

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

I . FFMPEG 播放视频流程


FFMPEG 播放视频流程 : 视频中包含图像和音频 ;



① FFMPEG 初始化 : 参考博客 【Android FFMPEG 开发】FFMPEG 初始化 ( 网络初始化 | 打开音视频 | 查找音视频流 )


② FFMPEG 获取 AVStream 音视频流 : 参考博客 【Android FFMPEG 开发】FFMPEG 获取 AVStream 音视频流 ( AVFormatContext 结构体 | 获取音视频流信息 | 获取音视频流个数 | 获取音视频流 )


③ FFMPEG 获取 AVCodec 编解码器 : 参考博客 【Android FFMPEG 开发】FFMPEG 获取编解码器 ( 获取编解码参数 | 查找编解码器 | 获取编解码器上下文 | 设置上下文参数 | 打开编解码器 )


④ FFMPEG 读取音视频流中的数据到 AVPacket : 参考博客 【Android FFMPEG 开发】FFMPEG 读取音视频流中的数据到 AVPacket ( 初始化 AVPacket 数据 | 读取 AVPacket )


⑤ FFMPEG 解码 AVPacket 数据到 AVFrame ( 音频 / 视频数据解码 ) : 参考博客 【Android FFMPEG 开发】FFMPEG 解码 AVPacket 数据到 AVFrame ( AVPacket->解码器 | 初始化 AVFrame | 解码为 AVFrame 数据 )


⑥ FFMPEG AVFrame 图像格式转换 YUV -> RGBA : 参考博客 【Android FFMPEG 开发】FFMPEG AVFrame 图像格式转换 YUV -> RGBA ( 获取 SwsContext | 初始化图像数据存储内存 | 图像格式转换 )


⑦ FFMPEG ANativeWindow 原生绘制 准备 : 参考博客 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( Java 层获取 Surface | 传递画布到本地 | 创建 ANativeWindow )


⑧ FFMPEG ANativeWindow 原生绘制 : 参考博客 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( 设置 ANativeWindow 缓冲区属性 | 获取绘制缓冲区 | 填充数据到缓冲区 | 启动绘制 )


⑨ FFMPEG 音频重采样 : 参考博客 【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert )




II . OpenSLES 播放音频流程


OpenSLES 播放音频流程 :



〇 视频播放操作 : FFMPEG 环境初始化 , 获取 AVStream 音视频流 , 获取 AVCodec 编解码器 , 读取音视频流中的数据到 AVPacket , 解码 AVPacket 数据到 AVFrame , AVFrame 图像格式转换 YUV -> RGBA , ANativeWindow 原生绘制 ;



〇 音频播放操作 : FFMPEG 环境初始化 , 获取 AVStream 音视频流 , 获取 AVCodec 编解码器 , 读取音视频流中的数据到 AVPacket , 解码 AVPacket 数据到 AVFrame , 音频重采样 , 然后使用 OpenSLES 播放重采样后的音频 ;



① 创建引擎 : 先创建引擎对象 , 再实现引擎对象 , 最后从引擎对象中 , 获取引擎接口 ;


SLresult result;


// 创建引擎
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
// 实现引擎
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
// 获取引擎接口
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);



② 设置输出混音器 : 创建输出混音器对象 , 实现输出混音器 ;


// 创建输出混音器对象 , 可以指定一个混响效果参数 ( 该混淆参数可选 )
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);
// 实现输出混音器
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);



③ 获取混响接口并设置混响 : 该步骤不是必须操作 , 另外获取混响接口可能失败 ;


// 获取混响接口
result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                                           &outputMixEnvironmentalReverb);
// 设置混响
if (SL_RESULT_SUCCESS == result) {
    result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
            outputMixEnvironmentalReverb, &reverbSettings);
    (void)result;
}



④ 配置音源输入 : 配置音频数据源缓冲队列 , 和 音源格式 ( 采样率 , 样本位数 , 通道数 , 样本大小端格式 ) ;


//1 . 配置音源输入


// 配置要播放的音频输入缓冲队列属性参数 , 缓冲区大小 , 音频格式 , 采样率 , 样本位数 , 通道数 , 样本大小端格式
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
// 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};



⑤ 配置音频输出 : 装载输出混音器对象 到 SLDataLocator_OutputMix , 在将 SLDataLocator_OutputMix 结构体装载到 SLDataSink 中 ;


// 配置混音器 : 将 outputMixObject 混音器对象装载入 SLDataLocator_OutputMix 结构体中
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
// 将 SLDataLocator_OutputMix 结构体装载到 SLDataSink 中
//  音频输出通过 loc_outmix 输出 , 实际上是通过 outputMixObject 混音器对象输出的
SLDataSink audioSnk = {&loc_outmix, NULL};



⑥ 创建并实现播放器 : 先使用 引擎 , 音源输入 , 音频输出 , 采样率 , 接口队列ID 等参数创建播放器 , 再实现播放器对象 ;


// 操作队列接口 , 如果需要 特效接口 , 添加 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);
// 创建播放器对象
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);



⑦ 获取播放器接口 和 缓冲队列接口 : 获取的接口 对应 播放器创建时的接口 ID 数组参数 ;


// 获取播放器 Player 接口 : 该接口用于设置播放器状态 , 开始 暂停 停止 播放 等操作
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
// 获取播放器 缓冲队列 接口 : 该接口用于控制 音频 缓冲区数据 播放
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
                                         &bqPlayerBufferQueue);



⑧ 注册回调函数 : 按照指定的回调函数类型 , 声明并实现该回调函数 , 并将该回调函数注册给播放器缓冲队列接口 ;


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


回调函数类型 :


typedef void (SLAPIENTRY *slAndroidSimpleBufferQueueCallback)(
  SLAndroidSimpleBufferQueueItf caller,
  void *pContext
);


回调函数实现 :


//每当缓冲数据播放完毕后 , 会自动回调该回调函数
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
  ...
    //通过播放器队列接口 , 将 PCM 数据加入到该队列缓冲区后 , 就会自动播放这段音频
    (*bq)->Enqueue(bq, audioChannel->data, data_size);
}


⑨ 获取效果器接口 和 音量控制接口 : 这两个接口不是必须的 , 可选选项 ;


 

// 获取效果器发送接口 ( get the effect send interface )
    bqPlayerEffectSend = NULL;
    if( 0 == bqPlayerSampleRate) {
        result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND,
                                                 &bqPlayerEffectSend);
    }
    // 获取音量控制接口 ( get the volume interface ) [ 如果需要调节音量可以获取该接口 ]
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);



⑩ 设置播放状态 : 设置播放状态为 SL_PLAYSTATE_PLAYING ;


// 设置播放器正在播放状态 ( set the player's state to playing )
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);


⑪ 手动调用激活回调函数 : 第一次激活回调函数调用 , 需要手动激活 ;


// 手动激活 , 手动调用一次 bqPlayerCallback 回调函数
bqPlayerCallback(bqPlayerBufferQueue, this);




III . OpenSLES 播放参考 Google 官方示例


1 . Google 官方示例 : 关于 OpenSL ES 音频播放 , 在 Google 的官方示例 native-audio 中 , 有现成的代码可供使用 ;



① Google 官方示例 参考地址 : native-audio


② OpenSL ES 播放代码 : native-audio-jni.c



2 . FFMPEG 播放 : 在 FFMPEG 中可以原封不动的拷贝 native-audio 项目中的关于 OpenSL ES 播放相关的代码 , 但是在 slAndroidSimpleBufferQueueCallback 回调函数中播放的音频 , 是 FFMPEG 中音频从 AVPacket 解码成的 AVFrame 重采样后的音频 , 关于音频重采样参考 【Android FFMPEG 开发】FFMPEG 音频重采样 ( 初始化音频重采样上下文 SwrContext | 计算音频延迟 | 计算输出样本个数 | 音频重采样 swr_convert ) ;


目录
相关文章
|
3月前
|
XML API Android开发
码农之重学安卓:利用androidx.preference 快速创建一、二级设置菜单(demo)
本文介绍了如何使用androidx.preference库快速创建具有一级和二级菜单的Android设置界面的步骤和示例代码。
122 1
码农之重学安卓:利用androidx.preference 快速创建一、二级设置菜单(demo)
|
2月前
|
Android开发
Android经典实战之Textview文字设置不同颜色、下划线、加粗、超链接等效果
本文介绍了 `SpannableString` 在 Android 开发中的强大功能,包括如何在单个字符串中应用多种样式,如颜色、字体大小、风格等,并提供了详细代码示例,展示如何设置文本颜色、添加点击事件等,助你实现丰富文本效果。
255 3
|
2月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
152 0
|
3月前
|
开发工具 Android开发
Android项目架构设计问题之外部客户方便地设置回调如何解决
Android项目架构设计问题之外部客户方便地设置回调如何解决
28 0
|
30天前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
106 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
1月前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
57 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
1月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
139 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
1月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
74 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
|
2月前
|
XML Java Android开发
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
GSYVideoPlayer是一款国产移动端视频播放器,支持弹幕、滤镜、广告等功能,采用IJKPlayer、Media3(EXOPlayer)、MediaPlayer及AliPlayer多种内核。截至2024年8月,其GitHub星标数达2万。集成时需使用新版Android Studio,并按特定步骤配置依赖与权限。提供了NormalGSYVideoPlayer、GSYADVideoPlayer及ListGSYVideoPlayer三种控件,支持HLS、RTMP等多种直播链接。
100 18
FFmpeg开发笔记(五十二)移动端的国产视频播放器GSYVideoPlayer
|
1月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
85 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库

热门文章

最新文章

下一篇
无影云桌面