之前已经完成了视频的播放,下面将完成音频的播放。
一、音频解码
1、首先在fFpeng类中会开启一个不断读取packet的子线程,并且把相应packet类型(视频类型,音频类型)存放到不同的音视频frame队列中。和之前视频播放一样,这里添加音频的代码。代码如下:
void QbFFmpeg::start() {
isPlaying = true;
//视频开始播放 交给视频对象处理
if (videoChannel2) {
videoChannel2->play();
}
//音频开始播放 交给音频对象处理
if(audioChannel){
audioChannel->play();
}
//开始解码线程
pthread_create(&playThread, NULL, play, this);
}
2、调用audioChannel->play()的方法,代码如下:
void *openSLInit(void *args) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
audioChannel->initOpenSLES();
return 0;
}
void *decodeAudio(void *args) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
audioChannel->decodePackets();
return 0;
}
void AudioChannel::play() {
//将音频数据转为pcm格式。因为喇叭播放的格式要统一为pcm格式 比如aac和MP3格式他们的编码格式是不一样的转,需要转换为统一格式
//声明转换上下文
swr_ctx = 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(swr_ctx);
pkt_queue.setWork(1);
frame_queue.setWork(1);
isPlaying = true;
//创建初始化openSL ES 线程
pthread_create(&pid_opensl_init, NULL, openSLInit, this);
//创建音频解码线程
pthread_create(&pid_opensl_init, NULL, decodeAudio, this);
}
3、调用play方法后,会启动两个线程,初始化openSLES线程和音频解码线程。初始化代码如下:
/**
* 子线程中初始化openSL ES 分为6个步骤
*/
void AudioChannel::initOpenSLES() {
//音频引擎
SLEngineItf engineInterface = NULL;
//音频对此昂
SLObjectItf engineObject = NULL;
//混音器
SLObjectItf outputMixObject = NULL;
//播放器
SLObjectItf bqPlayerObject = NULL;
// 回调接口
SLPlayItf bqPlayerInterface = NULL;
// 缓冲队列
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;
//todo 第一步、初始化播放引擎
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;
}
//音频接口 相当于SurfaceHodler
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);
if (SL_RESULT_SUCCESS != result) {
return;
}
//todo 第二步、设置混音器
result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
// 初始化混音器outputMixObject
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
return;
}
//todo 第三步、创建播放器
SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2};
//pcm数据格式
SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM//播放pcm格式的数据
, 2,//2个声道(立体声)
SL_SAMPLINGRATE_44_1, //44100hz的频率
SL_PCMSAMPLEFORMAT_FIXED_16,//位数 16位
SL_PCMSAMPLEFORMAT_FIXED_16,//和位数一致就行
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,//立体声(前左前右)
SL_BYTEORDER_LITTLEENDIAN//小端模式
};
//
SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&outputMix, NULL};
SLDataSource slDataSource = {&android_queue, &pcm};
const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
(*engineInterface)->CreateAudioPlayer(engineInterface, &bqPlayerObject// //播放器
, &slDataSource//播放器参数 播放缓冲队列 播放格式
, &audioSnk,//播放缓冲区
1,//播放接口回调个数
ids,//设置播放队列ID
req//是否采用内置的播放队列
);
//初始化播放器
(*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
//todo 第四步、设置缓存队列和回调函数
// 得到接口后调用 获取Player接口
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerInterface);
// 获得播放器接口
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
&bqPlayerBufferQueue);
(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
// todo 第五步、设置播放状态,需要手动进行设置
(*bqPlayerInterface)->SetPlayState(bqPlayerInterface, SL_PLAYSTATE_PLAYING);
//todo 第六步、启动回调函数 只要启动就会不断的被回调 把pcm数据缓存到缓冲区中
bqPlayerCallback(bqPlayerBufferQueue, this);
LOGE("--- 手动调用播放 packet:%d", this->pkt_queue.size());
}
/**
* 音频解码
*/
void AudioChannel::decodePackets() {
AVPacket *packet = 0;
while (isPlaying) {
//从队列中取一个
int ret = pkt_queue.deQueue(packet);
if (!isPlaying) {
break;
}
if (!ret) {
continue;
}
ret = avcodec_send_packet(avCodecContext, packet);
releaseAvPacket(packet);
if (ret == AVERROR(EAGAIN)) {
//需要更多数据
continue;
} else if (ret < 0) {
//失败
break;
}
AVFrame *frame = av_frame_alloc();
ret = avcodec_receive_frame(avCodecContext, frame);
if (ret == AVERROR(EAGAIN)) {
//需要更多数据
continue;
} else if (ret < 0) {
break;
}
while (frame_queue.size() > 100 && isPlaying) {
av_usleep(1000 * 10);
continue;
}
//packet包解码后放入帧队列
frame_queue.enQueue(frame);
}
}
4、OpenSL使用回调机制来访问音频IO,但不像跟Jack、CoreAudio那些音频异步IO框架,OpenSL 的回调里并不会把音频数据作为参数传递,回调方法仅仅是告诉我们:BufferQueue已经就绪,可以接受/获取数据了。这里的回调函数是bqPlayerCallback(bqPlayerBufferQueue, this);会不断地从bufferQueue中获取音频数据,交给喇叭播放。代码如下:
/**
* openSLES回调函数
*/
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(context);
//获取pcm缓冲队列大小
int dataLength = audioChannel->changeFrameToPcm();
if(dataLength >0 ){
//送入音频播放队列播放 喇叭会从队列中主动去拿这些数据进行播放
(*bq)->Enqueue(bq,audioChannel->buffer,dataLength);
}
}
5、上面首先将从buffer中去除的数据传为可播放的pcm数据。因为对于不同格式的数据(比如aac格式、MP4格式)所支持的播放设备是不一样的,只有将他们转为统一的pcm数据格式才能进行播放。转化代码如下:
/**
* 转换为pcm数据
*/
int AudioChannel::changeFrameToPcm() {
//播放的时候才需要转换
AVFrame *frame = 0;
int data_size = 0;
while (isPlaying) {
int ret = frame_queue.deQueue(frame);
if (!isPlaying) {
break;
}
if (!ret) {
continue;
}
//设置转换的一些参数
uint64_t dst_nb_samples = av_rescale_rnd(
swr_get_delay(swr_ctx, frame->sample_rate) + frame->nb_samples,
out_sample_rate,
frame->sample_rate,
AV_ROUND_UP);
// 转换,返回值为转换后的sample个数 buffer malloc(size) 转换的数据放到buffer中
int nb = swr_convert(swr_ctx, &buffer, dst_nb_samples,
(const uint8_t **) frame->data, frame->nb_samples);
//计算buffer大小
data_size = nb * out_channels * out_samplesize;
//数量*单位 pts是数量
clock = frame->pts*av_q2d(time_base);
break;
}
releaseAvFrame(frame);
return data_size;
}
6、集成以上代码,第4步(*bq)->Enqueue(bq,audioChannel->buffer,dataLength);执行以后,函数往音频设备放入数据,就能实现播放了。