FFmpeg实现Android音视频播放器【1】
1、音视频数据解封包
这一步主要是将视频文件中的视频和音频数据进行拆分。首先获取AVFormatContext对象,通过AVFormatContext打开音视频数据源(.mp4文件或者音视频流)
formatContext = avformat_alloc_context();
int r = avformat_open_input(&formatContext, data_source, nullptr, &dictionary);
2、查找媒体中的音视频流的信息
解封包成功后,formatContext中会有多条数据流,循环遍历数据流,获取音频和视频信息。最后获取解码器AVCodec,将音视频文件和解码器分别传给video和audio子线程处理
AVStream *stream = formatContext->streams[stream_index];
AVCodecParameters *parameters = stream->codecpar;
AVCodec *codec = avcodec_find_decoder(parameters->codec_id);
codecContext = avcodec_alloc_context3(codec);
r = avcodec_parameters_to_context(codecContext, parameters);
r = avcodec_open2(codecContext, codec, nullptr);
if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
// 是音频
audio_channel = new AudioChannel(stream_index, codecContext, time_base);
} else if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO) {
// 是视频
video_channel = new VideoChannel(stream_index, codecContext, time_base, fps);
}
3、视频流处理流程
前面步骤获取的视频信息,还属于压缩数据,需要在这一步,进行解码,得到yuv格式数据,然后再将yuv格式转换成rgba格式数据才能播放。所以这里采用的双队列数据结构处理,线程1往pkt队列放入压缩数据,线程2从pkt队列中获取压缩数据进行解码,然后放入frame队列中,线程3从fram队列中获取yuv数据将其转换成rgba数据
//线程1将压缩数据插入pkt队列
video_channel->packets.insertToQueue(packet);
//线程2从pkt队列中获取压缩数据
int ret = packets.getQueueAndDel(pkt);
//将pkt数据解码为frame(此时为yuv格式)
ret = avcodec_send_packet(codecContext, pkt);
ret = avcodec_receive_frame(codecContext, frame);
frames.insertToQueue(frame);
//将yuv格式转rgba格式
sws_getContext(
codecContext->width,
codecContext->height,
codecContext->pix_fmt,
codecContext->width,
codecContext->height,
AV_PIX_FMT_RGBA,
SWS_BILINEAR, NULL, NULL, NULL);
sws_scale(sws_ctx,
frame->data, frame->linesize,
0, codecContext->height,
dst_data,
dst_linesize
);
4、视频播放
视频播放在android端主要是通过nativewindow来实现,我们可以简单的理解为,我们只需要将一帧一帧的frame交给nativewindow就可以了,这就是在播放。具体流程如下:
//创建ANativeWindow,通过jni调用,将android端的surface传递给C++层
window = ANativeWindow_fromSurface(env, surface);
// 设置窗口的大小,各个属性
ANativeWindow_setBuffersGeometry(window, width, height, WINDOW_FORMAT_RGBA_8888);
// 设置缓冲区 buffer
ANativeWindow_Buffer window_buffer; //
ANativeWindow_lock(window, &window_buffer, 0)
//将数据复制到buffer,即为播放
uint8_t *dst_data = static_cast<uint8_t *>(window_buffer.bits);
int dst_linesize = window_buffer.stride * 4;
for (int i = 0; i < window_buffer.height; ++i) {
memcpy(dst_data + i * dst_linesize, src_data + i * src_lineSize, dst_linesize); }
5、音频流处理
首先同样的,音频的压缩数据也需要先进行解码生成pcm格式的原始数据,然后在对pcm数据进行重采样。我们知道音频数据三要素:采样率,位声,声道数。重采样就是将这三要素与播放设备的三要素保持一致,否则会出现电流声
// 初始化 重采样上下文SwrContext
out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); // STEREO:双声
out_sample_size = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); // 每个sample是16 bit == 2字节
out_sample_rate = 44100;
// out_buffers_size = 176,400
out_buffers_size = out_sample_rate * out_sample_size * out_channels; // 44100 * 2 * 2 = 176,400
out_buffers = static_cast<uint8_t *>(malloc(out_buffers_size));
swr_ctx = swr_alloc_set_opts(0,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_S16,
out_sample_rate,
codecContext->channel_layout,
codecContext->sample_fmt,
codecContext->sample_rate,
0, 0);
swr_init(swr_ctx);
// 重采样
int 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);
int samples_per_channel = swr_convert(swr_ctx,
&out_buffers,
dst_nb_samples,
(const uint8_t **) frame->data,
frame->nb_samples);
6、音频流播放
音频播放过程较为复杂,需要初始化引擎,设置混音器,设置播放器等操作,但都是固定代码,具体流程如下
SLresult result; // 用于接收 执行成功或者失败的返回值
/**
* TODO 1.创建引擎对象并获取【引擎接口】
*/
// 1.1 创建引擎对象:SLObjectItf engineObject
result = slCreateEngine(&engineObject, 0, 0, 0, 0, 0);
if (SL_RESULT_SUCCESS != result) {
LOGE("创建引擎 slCreateEngine error");
return;
}
// 1.2 初始化引擎
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); // SL_BOOLEAN_FALSE:延时等待你创建成功
if (SL_RESULT_SUCCESS != result) {
LOGE("创建引擎 Realize error");
return;
}
// 1.3 获取引擎接口
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);
if (SL_RESULT_SUCCESS != result) {
LOGE("创建引擎接口 Realize error");
return;
}
if (engineInterface) {
LOGD("创建引擎接口 create success");
} else {
LOGD("创建引擎接口 create error");
return;
}
/**
* TODO 2.设置混音器
*/
// 2.1 创建混音器
result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
if (SL_RESULT_SUCCESS != result) {
LOGD("初始化混音器 CreateOutputMix failed");
return;
}
// 2.2 初始化混音器
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); // SL_BOOLEAN_FALSE:延时等待你创建成功
if (SL_RESULT_SUCCESS != result) {
LOGD("初始化混音器 (*outputMixObject)->Realize failed");
return;
}
LOGI("2、设置混音器 Success");
/**
* TODO 3.创建播放器
*/
// 创建buffer缓存类型的队列 2的队列大小
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
10};
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, // 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};
// 设置混音器
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; // SL_DATALOCATOR_OUTPUTMIX:输出混音器类型
SLDataSink audioSnk = {&loc_outmix, NULL}; //
const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
// 创建播放器 SLObjectItf bqPlayerObject
result = (*engineInterface)->CreateAudioPlayer(engineInterface,
&bqPlayerObject,
&audioSrc,
&audioSnk,
//
1,
ids,
req
);
if (SL_RESULT_SUCCESS != result) {
LOGD("创建播放器 CreateAudioPlayer failed!");
return;
}
// 3.4 初始化播放器:SLObjectItf bqPlayerObject
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); // SL_BOOLEAN_FALSE:延时等待你创建成功
if (SL_RESULT_SUCCESS != result) {
LOGD("实例化播放器 CreateAudioPlayer failed!");
return;
}
LOGD("创建播放器 CreateAudioPlayer success!");
// 3.5 获取播放器接口
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); // SL_IID_PLAY:播放接口 == iplayer
if (SL_RESULT_SUCCESS != result) {
LOGD("获取播放接口 GetInterface SL_IID_PLAY failed!");
return;
}
LOGI("3、创建播放器 Success");
/**
* TODO 4.设置回调函数
*/
// 4.1 获取播放器队列接口:SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue // 播放需要的队列
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
if (result != SL_RESULT_SUCCESS) {
LOGD("获取播放队列 GetInterface SL_IID_BUFFERQUEUE failed!");
return;
}
// 4.2 设置回调 void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, // 传入刚刚设置好的队列
bqPlayerCallback, // 回调函数
this); // 给回调函数的参数
LOGI("4、设置播放回调函数 Success");
/**
* TODO 5、设置播放器状态为播放状态
*/
(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
LOGI("5、设置播放器状态为播放状态 Success");
/**
* TODO 6、手动激活回调函数
*/
bqPlayerCallback(bqPlayerBufferQueue, this);
以上为音视频播放的主体流程,还有很多细节没有探讨,如音频和视频都开了多个线程来处理,声音和画面肯定是不同步的,需要处理。另外内存开销方面,原始帧frame数据非常大,也需要进行优化。将在下一节分享。