前两篇文章分别解码了直播文件,和对解析文件的视频进行解码与播放
NDK24_FFmpeg集成及初始化
NDK25_FFmpeg视频解码与原生绘制
本片文章将继续对音频文件进行解码
一 原理
- 音频与视频的解编码方式一致
- 音频的播放使用OpenSL
NDK26_OpenSL
二 步骤
-
DNFFmpeg->_prepare方法设置音频AVCodecContext
-
DNFFmpeg->_start将音频数据包保存audioChannel的队列packets
-
audioChannel->play()启动解码与播放
-
audioChannel->audio_decode 解码
开启线程执行decode方法,循环从队列packets中取数据,然后将解码成功的数据存到队列frames中
-
audioChannel->audio_play 播放
开启线程执行_play方法,创建引擎、设置混音器、创建播放器,设置播放回调函数,在回调函数中循环从队列frames中取数据,激活播放
三 代码实现
1 引入OpenSLES库
CmakeList.txt
target_link_libraries(native-lib
avfilter avformat avcodec avutil swresample swscale
${log-lib} z android OpenSLES)
2 FFmpeg:Decode 与启动
// Created by Administrator on 2018/9/5.
//
#include <cstring>
#include <pthread.h>
#include "DNFFmpeg.h"
#include "macro.h"
void *task_prepare(void *args) {
DNFFmpeg *ffmpeg = static_cast<DNFFmpeg *>(args);
ffmpeg->_prepare();
return 0;
}
DNFFmpeg::DNFFmpeg(JavaCallHelper *callHelper, const char *dataSource) {
this->callHelper = callHelper;
//防止 dataSource参数 指向的内存被释放
//strlen 获得字符串的长度 不包括\0
this->dataSource = new char[strlen(dataSource) + 1];
strcpy(this->dataSource, dataSource);
}
DNFFmpeg::~DNFFmpeg() {
//释放
DELETE(dataSource);
DELETE(callHelper);
}
void DNFFmpeg::prepare() {
//创建一个线程
pthread_create(&pid, 0, task_prepare, this);
}
void DNFFmpeg::_prepare() {
// 初始化网络 让ffmpeg能够使用网络
avformat_network_init();
//1、打开媒体地址(文件地址、直播地址)
// AVFormatContext 包含了 视频的 信息(宽、高等)
formatContext = avformat_alloc_context();
//文件路径不对 手机没网
int ret = avformat_open_input(&formatContext, dataSource, 0, 0);
//ret不为0表示 打开媒体失败
if (ret != 0) {
LOGE("打开媒体失败:%s", av_err2str(ret));
callHelper->onError(THREAD_CHILD, FFMPEG_CAN_NOT_OPEN_URL);
return;
}
//2、查找媒体中的 音视频流 (给 contxt里的 streams等成员赋)
ret = avformat_find_stream_info(formatContext, 0);
// 小于0 则失败
if (ret < 0) {
LOGE("查找流失败:%s", av_err2str(ret));
callHelper->onError(THREAD_CHILD, FFMPEG_CAN_NOT_FIND_STREAMS);
return;
}
//nb_streams :几个流(几段视频/音频)
for (int i = 0; i < formatContext->nb_streams; ++i) {
//可能代表是一个视频 也可能代表是一个音频
AVStream *stream = formatContext->streams[i];
//包含了 解码 这段流 的各种参数信息(宽、高、码率、帧率)
AVCodecParameters *codecpar = stream->codecpar;
//无论视频还是音频都需要干的一些事情(获得解码器)
// 1、通过 当前流 使用的 编码方式,查找解码器
AVCodec *dec = avcodec_find_decoder(codecpar->codec_id);
if (dec == NULL) {
LOGE("查找解码器失败:%s", av_err2str(ret));
callHelper->onError(THREAD_CHILD, FFMPEG_FIND_DECODER_FAIL);
return;
}
//2、获得解码器上下文
AVCodecContext *context = avcodec_alloc_context3(dec);
if (context == NULL) {
LOGE("创建解码上下文失败:%s", av_err2str(ret));
callHelper->onError(THREAD_CHILD, FFMPEG_ALLOC_CODEC_CONTEXT_FAIL);
return;
}
//3、设置上下文内的一些参数 (context->width)
// context->width = codecpar->width;
// context->height = codecpar->height;
ret = avcodec_parameters_to_context(context, codecpar);
//失败
if (ret < 0) {
LOGE("设置解码上下文参数失败:%s", av_err2str(ret));
callHelper->onError(THREAD_CHILD, FFMPEG_CODEC_CONTEXT_PARAMETERS_FAIL);
return;
}
// 4、打开解码器
ret = avcodec_open2(context, dec, 0);
if (ret != 0) {
LOGE("打开解码器失败:%s", av_err2str(ret));
callHelper->onError(THREAD_CHILD, FFMPEG_OPEN_DECODER_FAIL);
return;
}
//音频
if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
//0
audioChannel = new AudioChannel(i,context);
} else if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
//1
videoChannel = new VideoChannel(i,context);
videoChannel->setRenderFrameCallback(callback);
}
}
//没有音视频 (很少见)
if (!audioChannel && !videoChannel) {
callHelper->onError(THREAD_CHILD, FFMPEG_NOMEDIA);
return;
}
// 准备完了 通知java 你随时可以开始播放
callHelper->onPrepare(THREAD_CHILD);
};
void *play(void *args) {
DNFFmpeg *ffmpeg = static_cast<DNFFmpeg *>(args);
ffmpeg->_start();
return 0;
}
void DNFFmpeg::start() {
// 正在播放
isPlaying = 1;
if (videoChannel){
videoChannel->play();
}
//启动声音的解码与播放
if (audioChannel){
audioChannel->play();
}
pthread_create(&pid_play, 0, play, this);
}
/**
* 专门读取数据包
*/
void DNFFmpeg::_start() {
//1、读取媒体数据包(音视频数据包)
int ret;
while (isPlaying) {
AVPacket *packet = av_packet_alloc();
ret = av_read_frame(formatContext, packet);
//=0成功 其他:失败
if (ret == 0) {
//stream_index 这一个流的一个序号
if (audioChannel && packet->stream_index == audioChannel->id) {
audioChannel->packets.push(packet);
} else if (videoChannel && packet->stream_index == videoChannel->id) {
videoChannel->packets.push(packet);
}
} else if (ret == AVERROR_EOF) {
//读取完成 但是可能还没播放完
} else {
//
}
}
};
void DNFFmpeg::setRenderFrameCallback(RenderFrameCallback callback){
this->callback = callback;
}
2 AudioChannel 开启线程循环解析与播放
AudioChannel.h
//
// Created by PF0ZYBAJ on 2020-9-9.
//
#ifndef PLAYER_AUDIOCHANNEL_H
#define PLAYER_AUDIOCHANNEL_H
#include "BaseChannel.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
extern "C"{
#include <libswresample/swresample.h>
};
class AudioChannel : public BaseChannel {
public:
AudioChannel(int id, AVCodecContext *context);
~AudioChannel();
void play();
void decode();
void _play();
int getPcm();
public:
uint8_t *data = 0;
int out_channels;
int out_samplesize;
int out_sample_rate;
private:
pthread_t pid_audio_decode;
pthread_t pid_audio_play;
/**
* OpenSL ES
*/
//引擎与引擎接口
SLObjectItf engineObject = 0;
SLEngineItf engineInterface = 0;
//混音器
SLObjectItf outputMixObject = 0;
//播放器
SLObjectItf bqPlayerObject = 0;
//播放器接口
SLPlayItf bqPlayerInterface = 0;
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueueInterface = 0;
//重采样
SwrContext *swrContext = 0;
};
#endif //PLAYER_AUDIOCHANNEL_H
AudioChannel.cpp
//
// Created by PF0ZYBAJ on 2020-9-9.
//
#include "AudioChannel.h"
void *audio_decode(void *args) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
audioChannel->decode();
return 0;
}
void *audio_play(void *args) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
audioChannel->_play();
return 0;
}
AudioChannel::AudioChannel(int id, AVCodecContext *context) : BaseChannel(id, context) {
//根据布局获取声道数
out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
out_samplesize = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
out_sample_rate = 44100;
//44100个16位 44100 * 2
// 44100*(双声道)*(16位)
data = static_cast<uint8_t *>(malloc(out_sample_rate * out_channels * out_samplesize));
memset(data,0,out_sample_rate * out_channels * out_samplesize);
}
AudioChannel::~AudioChannel() {
if (data) {
free(data);
data = 0;
}
}
void AudioChannel::play() {
//设置为播放状态
packets.setWork(1);
frames.setWork(1);
//0+输出声道+输出采样位+输出采样率 +输入的3个参数
swrContext = swr_alloc_set_opts(0, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, out_sample_rate,
avCodecContext->channel_layout, avCodecContext->sample_fmt,
avCodecContext->sample_rate, 0, 0);
swr_init(swrContext);
isPlaying = 1;
//1 、解码
pthread_create(&pid_audio_decode, 0, audio_decode, this);
//2、 播放
pthread_create(&pid_audio_play, 0, audio_play, this);
}
void AudioChannel::decode() {
AVPacket *packet = 0;
while (isPlaying) {
//取出一个数据包
int ret = packets.pop(packet);
if (!isPlaying) {
break;
}
//取出失败
if (!ret) {
continue;
}
//把包丢给解码器
ret = avcodec_send_packet(avCodecContext, packet);
releaseAvPacket(&packet);
//重试
if (ret != 0) {
break;
}
//代表了一个图像 (将这个图像先输出来)
AVFrame *frame = av_frame_alloc();
//从解码器中读取 解码后的数据包 AVFrame
ret = avcodec_receive_frame(avCodecContext, frame);
//需要更多的数据才能够进行解码
if (ret == AVERROR(EAGAIN)) {
continue;
} else if (ret != 0) {
break;
}
//再开一个线程 来播放 (流畅度)
frames.push(frame);
}
releaseAvPacket(&packet);
};
//返回获取的pcm数据大小
int AudioChannel::getPcm() {
int data_size = 0;
AVFrame *frame;
int ret = frames.pop(frame);
if (!isPlaying) {
if (ret) {
releaseAvFrame(&frame);
}
return data_size;
}
//4800HZ 8位 -》44100 16 位
//重采样
//假设我们输入了10个数据,swrContext转码器这一次处理了8个数据
//那么如果不加delays(上次没处理完的数据),积压
int64_t delays = swr_get_delay(swrContext,frame->sample_rate);
//将frame->nb_samples 个数据由sample_rate采样率转成44100 后 返回多少个数据
//10 个 4800 = nb 个44100
int64_t max_samples = av_rescale_rnd(delays+frame->nb_samples,out_sample_rate,frame->sample_rate,AV_ROUND_UP);
//上下文+输出缓冲区+输出缓冲区能接受的最大数据量+输入数据+输入数据个数
//返回每一个声道的输出数据
int samples = swr_convert(swrContext, &data, max_samples, (const uint8_t **)frame->data, frame->nb_samples);
//44100*2(声道数)多少个16位数据
//获得 samples个 2字节(16位)*2声道
data_size = samples * out_samplesize * out_channels ;
return data_size;
}
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(context);
//获得pcm 数据 多少个字节 data
int dataSize = audioChannel->getPcm();
if(dataSize > 0 ){
// 接收16位数据
(*bq)->Enqueue(bq,audioChannel->data,dataSize);
}
}
void AudioChannel::_play() {
/**
* 1 创建引擎并获取引擎接口
*/
SLresult result;
//1.1 创建引擎
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
if (SL_RESULT_SUCCESS != result) {
return;
}
//1.2 初始化引擎
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
return;
}
//1.3 获取引擎接口SLEngineItf engineInterface
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);
if (SL_RESULT_SUCCESS != result) {
return;
}
/**
* 2 设置混音器
*
*/
//2.1 创建混音器SLObjectItf outputMixObject
result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0,
0, 0);
if (SL_RESULT_SUCCESS != result) {
return;
}
//2.2 初始化混音器outputMixObject
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
return;
}
/**
* 3 创建播放器
*/
//3.1 匹配输入声音信息
//创建buffer缓冲类型的队列 2个队列
SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2};
//pcm数据格式
//pcm+2(双声道)+44100(采样率)+ 16(采样位)+16(数据的大小)+LEFT|RIGHT(双声道)+小端数据
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};
//3.2 配置音轨(输出)
//设置混音器
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};
//3.3 创建播放器
(*engineInterface)->CreateAudioPlayer(engineInterface, &bqPlayerObject, &slDataSource,
&audioSnk, 1,
ids, req);
//初始化播放器
(*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
//得到接口后调用 获取Player接口
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerInterface);
/**
* 4 设置播放回调函数
*/
//获取播放器队列接口
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
&bqPlayerBufferQueueInterface);
//设置回调
(*bqPlayerBufferQueueInterface)->RegisterCallback(bqPlayerBufferQueueInterface,
bqPlayerCallback, this);
/**
* 5设置播放状态
*/
(*bqPlayerInterface)->SetPlayState(bqPlayerInterface, SL_PLAYSTATE_PLAYING);
/**
* 6 手动激活一下这个回调
*/
bqPlayerCallback(bqPlayerBufferQueueInterface, this);
}