Open SL ES 调用ffmpeg 播放声音(有注释用于回看)

资料

https://blog.csdn.net/ywl5320/article/details/78503768
http://www.cnblogs.com/renhui/p/9567332.html

AndroidStudio使用OpenSL ES官方配置文档
https://developer.android.com/ndk/guides/audio/getting-started.html

Open SL ES 和Open GL ES
一个是音频 一个是视频
OpenSLES官网
https://www.khronos.org/opensles/

官方文档
https://www.khronos.org/files/opensl-es-1-1-quick-reference.pdf

原生播放

OpenSL ES

OpenSL ES 全称是:Open Sound Library for Embedded Systems,

是一套无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API

它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能, 低响应时间的音频功能实现方法
,并实现软硬件音频性能的直接跨平台部署,降低执行难度,促进高级音频市场的发展

OpenSL ES 可以录音、播放音频URI和PCM数据,后面将编写 OpenSL ES 相关代码:
1、OpenSL ES 音频URI 播放
2、OpenSL ES 音频PCM数据 播放
3、OpenSL ES 录音 PCM数据

特点

  1. C 语言接口,兼容 C++,需要在 NDK 下开发,能更好地集成在 native 应用中
  2. 运行于 native 层,需要自己管理资源的申请与释放,没有 Dalvik 虚拟机的垃圾回收机制
  3. 支持 PCM 数据的播放,支持的配置:8bit/16bit 位宽,单通道/双通道,小端模式,采样率(8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 Hz)
  4. 支持播放的音频数据来源:res 文件夹下的音频、assets 文件夹下的音频、sdcard 目录下的音频、在线网络音频、代码中定义的音频二进制数据等等

不支持:
(1)不支持版本低于 Android 2.3 (API 9) 的设备
(2)没有全部实现 OpenSL ES 定义的特性和功能
(4)不支持直接播放 DRM 或者 加密的内容

我认为 openSL ES 主要的工作就是读取数组数据后直接调用驱动的程序,让音箱发出声音.

  • openSL ES 的so 库在 sdk\ndk-bundle\platforms\android-9\arch-arm\usr\lib\libOpenSLES.so

状态机制:

https://blog.csdn.net/brandon2015/article/details/51814657

image

三种状态:

  1. unrealized初始化状态
  2. realized 实现状态. 能够用的只能是这种状态
  3. suspended 暂停状态

举例: 正在播放音乐的时候 来电话了,音乐会中断播放手机铃声.

代码.
新建FFmpegMusic.cpp 把ffmpeg相关的操作都放在这里面. 主要是三个部分.

  1. 初始化 包括注册组件,初始化AVFormatContext,解码器AVCodecContext上下文,解码器AVCodec等等.
  2. 获取pcm数据
  3. 释放指针 回收内存

头文件FFmpegMusic.h

//
// Created by liuml on 2018/9/11.
//
#include <jni.h>
#include <string>
#include <jni.h>
#include <string>
#include <android/log.h>
//extern "C" 主要作用就是为了能够正确实现C++代码调用其他C语言代码 加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
//用于android 绘制图像的
#include <android/native_window_jni.h>
#include <unistd.h>
#include <libswresample/swresample.h>
}

#ifndef FFMPEGDEMO_FFMPEGMUSIC_H
#define FFMPEGDEMO_FFMPEGMUSIC_H
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"jnilib",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"jnilib",FORMAT,##__VA_ARGS__);
#endif //FFMPEGDEMO_FFMPEGMUSIC_H

int createFFmpge();

int getPcm();

void realseFFmpeg();

实现 FFmpegMusic.cpp

//
// Created by liuml on 2018/9/11.
//
#include <jni.h>
#include <string>
#include <jni.h>
#include <string>
#include <android/log.h>
#include "FFmpegMusic.h"

AVFormatContext *pContext;
AVCodecContext *pCodecCtx;
AVCodec *pCodex;
AVPacket *packet;
AVFrame *frame;
SwrContext *swrContext;
uint8_t *out_buffer;
int out_sample_rate;
int out_channer_nb = -1;
//找到视频流
int audio_stream_ids = -1;
/**
 * 初始化.   openSL ES 调用这个函数
 * @param rate 采样率
 * @param channel 通道数
 * @return
 */
int createFFmpeg(int *rate, int *channel) {
    //前面和之前一样 获取输入的信息 但是现在是找到音频的AVMEDIA_TYPE_AUDIO
    // TODO
    //无论编码还是解码 都要调用这个 注册各大组件
    av_register_all();

    //获取AVFormatContext  比特率 时长 文件路径 流的信息(nustream) 都封装在这里面
    pContext = avformat_alloc_context();

    char *input = "/sdcard/input.mp3";
    //AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options
    //上下文  文件名  打开文件格式 获取信息(AVDictionary)  凡是AVDictionary字典 都是获取视频文件信息
    if (avformat_open_input(&pContext, input, NULL, NULL) < 0) {
        LOGE("打开失败");
        return -1;
    }

    //给nbstram填充信息
    if (avformat_find_stream_info(pContext, NULL) < 0) {
        LOGE("获取信息失败");
        return -1;
    }


    for (int i = 0; i < pContext->nb_streams; ++i) {
        LOGE("循环 %d", i);
        //如果填充的视频流信息 -> 编解码,解码器 -> 流的类型 == 视频的类型
        //codec 每一个流 对应的解码上下文 codec_type 流的类型
        if (pContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_ids = i;
            LOGE("找到音频id %d", i);
            break;
        }
    }

    //解码器. mp3的解码器 解码器
    // ----------  获取到解码器上下文 获取视音频解码器
    pCodecCtx = pContext->streams[audio_stream_ids]->codec;
    LOGE("获取解码器上下文");
    //----------解码器
    pCodex = avcodec_find_decoder(pCodecCtx->codec_id);
    LOGE("获取解码器");
    //打开解码器  为什么avcodec_open2 版本升级的原因
    if (avcodec_open2(pCodecCtx, pCodex, NULL) < 0) {
        LOGE("解码失败");
        return -1;
    }

    //得到解封装 读取 解封每一帧 读取每一帧的压缩数据
    //初始化avpacket 分配内存  FFMpeg 没有自动分配内存 必须手动分匹配手动释放  不过有分配函数
    packet = (AVPacket *) av_malloc(sizeof(AVPacket));

    //初始化AVFrame
    frame = av_frame_alloc();

    //mp3 里面所包含的编码格式 转化成pcm
    //#include <libswresample/swresample.h>
    swrContext = swr_alloc();//获取转换的上下文

    int frame_count = 0;

    //AVFormatContext *s, AVPacket *pkt  上下文,avpacket 是数据包的意思
    //packet 入参
    int got_frame;

    int length = 0;

    //定义缓冲区输出的  需要多大的采样率  采样 44100  多少个字节.  双通道需要乘以2
    out_buffer = static_cast<uint8_t *>(av_malloc(44100 * 2));//一秒的缓冲区数量


    /**
     * struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
     *                                  //输出的
                                      int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
                                      //输入的
                                      int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
                                      //偏移量
                                      int log_offset, void *log_ctx);

     */
    //输出声道布局
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;//立体声
    //输出采样位数 16位 现在基本都是16位  位数越高 声音越清晰
    enum AVSampleFormat out_formart = AV_SAMPLE_FMT_S16;
    //输出的采样率 必须与输入的相同
    out_sample_rate = pCodecCtx->sample_rate;



    //https://blog.csdn.net/explorer_day/article/details/76332556  文档
    //swr_alloc_set_opts将PCM源文件的采样格式转换为自己希望的采样格式
    swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
                       pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate,
                       0, NULL);

    //初始化转换器
    swr_init(swrContext);

    //求出通道数
    out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);

    *rate = pCodecCtx->sample_rate;//通过解析出来的 采样率 赋值给调用的openSL ES 的采样率
    *channel = pCodecCtx->channels;//同理赋值 通道数.
    return 0 ;

}

/**
 * 获取pcm数据
 * @param pcm 二级指针 可以修改(缓冲区数组的地址)
 * @param size  大小
 * @return
 */
int getPcm(void **pcm,size_t *pcm_size) {
    int got_frame;
    int count = 0;
    //读取frame
    while (av_read_frame(pContext, packet) >= 0) {
        if (packet->stream_index == audio_stream_ids) {
            //解码  现在编码格式frame 需要转化成pcm
            int ret = avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
            LOGE("正在解码 %d", count++);
            if (ret < 0) {
                LOGE("解码完成");
            }
            //解码一帧
            if (got_frame > 0) {
                //真正的解码
                LOGE("开始解码");
                //转换得到out_buffer
                swr_convert(swrContext, &out_buffer, 44100 * 2,
                            (const uint8_t **) (frame->data), frame->nb_samples);

                LOGE("转换得到out_buffer");
                //求缓冲区实际的大小  通道数  frame->nb_samples 采样的点
                int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,
                                                      AV_SAMPLE_FMT_S16, 1);
                LOGE("求缓冲区实际的大小 %d", size);
                *pcm = out_buffer;
                *pcm_size = size;
                break;
            }
        }

    }
    LOGE("完成")

}

/**
 * 释放
 */
void releaseFFmpeg() {
    //回收
    av_free_packet(packet);
    av_free(out_buffer);

    av_frame_free(&frame);
    swr_free(&swrContext);
    avcodec_close(pCodecCtx);
    avformat_free_context(pContext);


}
  • 这里有个点 都是通过入参从而改变传递过来的参数.

一个概念 调用OpenSL ES 的方法 最终调用的是他的一个接口

例如:

创建一个播放器接口
 sLresult=(*bgPlayerObject)->GetInterface(bgPlayerObject, SL_IID_PLAY, &bqPlayerPlay);

OpenSL ES 的开发流程主要有如下6个步骤:

1、 创建接口对象

2、设置混音器

3、创建播放器(录音器)

4、设置缓冲队列和回调函数

5、设置播放状态

6、启动回调函数

上代码:

#include <jni.h>
#include <string>
#include <android/log.h>

//extern "C" 主要作用就是为了能够正确实现C++代码调用其他C语言代码 加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
//用于android 绘制图像的
#include <android/native_window_jni.h>
#include <unistd.h>
#include "libswresample/swresample.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
}

#include "FFmpegMusic.h"

#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"jnilib",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"jnilib",FORMAT,##__VA_ARGS__);

SLObjectItf engineObject = NULL;//用SLObjectItf声明引擎接口对象
SLEngineItf engineEngine = NULL;//声明具体的引擎对象实例
//混音器
SLObjectItf outputMixObject = NULL; //声明混音器接口对象
SLEnvironmentalReverbItf outputMixEnvironmentalReverbItf = NULL;//环境混响接口
SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_DEFAULT;//设置默认环境
//播放器
SLObjectItf bqPlayerObject;
//播放器接口
SLPlayItf bqPlayerPlay;
//缓冲器队列
SLAndroidSimpleBufferQueueItf bqPalyerQueue;

//音量对象
SLVolumeItf bqPlayerVolume;
//buffer数据
size_t bufferSize = 0;
void *buffer;

//当喇叭播放完声音回调此函数,添加pcm数据到缓冲区
void bqPlayerCallBack(SLAndroidSimpleBufferQueueItf bq, void *context) {
    bufferSize = 0;
    getPcm(&buffer, &bufferSize);//获取pcm数据
    if (NULL != buffer && 0 != bufferSize) {
        SLresult result;//结果
        //播放帧
        result = (*bqPalyerQueue)->Enqueue(bqPalyerQueue, buffer, bufferSize);
        LOGE("回调 bqPlayerCallback : %d", result);
    } else {
        LOGE("获取PCM失败")
    }
}

//openSL ES 播放音频
extern "C"
JNIEXPORT void JNICALL
Java_androidrn_ffmpegdemo_AudioPlayer_OpenSLEsPlay(JNIEnv *env, jobject instance) {

    SLresult sLresult;
    //初始化一个引擎
    sLresult = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    LOGE("初始化引擎 %d", sLresult);
    //改变成Realize状态,参数false 代表非异步,就是同步
    sLresult = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    LOGE("引擎改变成Realize状态 %d", sLresult);
    //获取到引擎接口 利用GetInterface 调用函数
    sLresult = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    LOGE("获取到引擎接口 %d", sLresult);
//    LOGE("引擎地址 &p", engineEngine);

    // ====混音器 设置 开始=====
    sLresult = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
    LOGE("混音器 设置 %d", sLresult);
    //同样切换状态  和上面同样的套路
    sLresult = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    LOGE("混音器 同样切换状态 %d", sLresult);
    //设置环境混响
    sLresult = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                                                &outputMixEnvironmentalReverbItf);
    LOGE("设置环境混响 %d", sLresult);
    //每个函数都会返回sLresult 用于判断是否调用成功
    if (SL_RESULT_SUCCESS == sLresult) {
        LOGE("环境混响成功");
        //设置环境
        (*outputMixEnvironmentalReverbItf)->SetEnvironmentalReverbProperties(
                outputMixEnvironmentalReverbItf, &settings);
    } else {
        LOGE("环境混响设置不成功 %d", sLresult);
//        return;
    }
    //===混音器设置结束 ====

    //初始化ffmpeg
    int rate;
    int channers;
    createFFmpeg(&rate, &channers);
    LOGE("初始化ffmpeg");


    //命名规则 都是SL 开头 比如像这个 把前面SLDataLocator 打出来了即可
    //pLocator -> SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE 这个是读取本地的 如果是网络的 则是 SL_DATALOCATOR_IODEVICE
    SLDataLocator_AndroidBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                                                      2};

    //SLDataSource的参数  *pAudioSrc
    /**
     * 	void *pLocator; 缓冲区队列 SLDataLocator_BufferQueue
	void *pFormat; 缓冲区队列装载什么数据格式 比如PCM 采样率 通道数 等
     */
    //pFormat 参数
    /*
     * SLuint32 		formatType; 源数据类型 PCM
	SLuint32 		numChannels; 通道数
	SLuint32 		samplesPerSec; 开始的采样率
	SLuint32 		bitsPerSample; 采样位数
	SLuint32 		containerSize;  包含位数
	SLuint32 		channelMask;    声道类型  立体声 左声道 右声道 环绕声
	SLuint32		endianness;  end结束标志位

     */
    SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM, channers, SL_SAMPLINGRATE_44_1,
                            SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
                            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
                            SL_BYTEORDER_LITTLEENDIAN};
    //    SLDataLocator_AndroidBufferQueue
//    SLDataLocator_AndroidSimpleBufferQueue
    //==CreateAudioPlayer 参数3
    SLDataSource slDataSource = {&android_queue, &pcm};

// 输出管道
    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    //混音器和播放器关联起来 链接方式
//    void *pLocator; 参数
//    void *pFormat;
    //和上面一样的参数
    SLDataSink audioSnk = {&outputMix, NULL};
    //声音增大减小 音量 调节输出
    const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};
    const SLboolean req[3]={SL_BOOLEAN_TRUE,SL_BOOLEAN_TRUE,SL_BOOLEAN_TRUE};
    LOGE("引擎  %p",engineEngine);
    /**
     * SLEngineItf self,    引擎
		SLObjectItf * pPlayer,  播放器
		SLDataSource *pAudioSrc,  数据源
		SLDataSink *pAudioSnk, 混音器和播放器关联起来 链接方式
		SLuint32 numInterfaces, 数量
		const SLInterfaceID * pInterfaceIds, 用于声音增大减小
		const SLboolean * pInterfaceRequired 和上面对应 如果是true 证明要被实体化  这里是3个boolean值
     */
    //得到播放器
    sLresult = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &slDataSource,
                                                  &audioSnk, 3,
                                                  ids, req);
    LOGE("  播放器 sLresult  %d ", sLresult);
    //bqPlayerObject 修改状态
    sLresult = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
    LOGE("  播放器 修改状态 sLresult  %d ", sLresult);
    //    得到接口后调用  获取Player接口 bqPlayerPlay
    sLresult = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
    LOGE("    得到接口后调用  获取Player接口 bqPlayerPlay sLresult  %d ", sLresult);
    //    注册回调缓冲区 //获取缓冲队列接口
    sLresult = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
                                               &bqPalyerQueue);
    LOGE("  注册回调缓冲区 //获取缓冲队列接口 sLresult  %d ", sLresult);
    //缓冲区接口回调  第二个参数是个函数
    sLresult = (*bqPalyerQueue)->RegisterCallback(bqPalyerQueue, bqPlayerCallBack,
                                                  NULL);
    LOGE("  缓冲区接口回调  sLresult  %d ", sLresult);
    //获取音量接口
    sLresult = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
    LOGE("  获取音量接口 sLresult  %d ", sLresult);
    //获取播放状态接口 设置播放状态
    sLresult = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
    LOGE("  获取播放状态接口 sLresult  %d ", sLresult);
    //播放第一帧
    bqPlayerCallBack(bqPalyerQueue, NULL);

}

//openSL ES 停止音频
extern "C"
JNIEXPORT void JNICALL
Java_androidrn_ffmpegdemo_AudioPlayer_OpenSlESStop(JNIEnv *env, jobject instance) {

    // destroy buffer queue audio player object, and invalidate all associated interfaces
    if (bqPlayerObject != NULL) {
        (*bqPlayerObject)->Destroy(bqPlayerObject);
        bqPlayerObject = NULL;
        bqPlayerPlay = NULL;
        bqPalyerQueue = NULL;
        bqPlayerVolume = NULL;
    }

    // destroy output mix object, and invalidate all associated interfaces
    if (outputMixObject != NULL) {
        (*outputMixObject)->Destroy(outputMixObject);
        outputMixObject = NULL;
        outputMixEnvironmentalReverbItf = NULL;
    }
    // destroy engine object, and invalidate all associated interfaces
    if (engineObject != NULL) {
        (*engineObject)->Destroy(engineObject);
        engineObject = NULL;
        engineEngine = NULL;
    }
    // 释放FFmpeg解码器相关资源
    releaseFFmpeg();

}

发现个问题 混响设置失败了一样能播放

解决:

 // ====混音器 设置 开始=====
    const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
    sLresult =(*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章