1、ffmpeg 播放音视频代码流程
第一步 、 解封装
1、avformat_alloc_context
初始化一个AVFormatContext 结构体
2、avformat_open_input(&avFormatContext, data_source, 0, &dictionary)
打开一个媒体源
3、avformat_find_stream_info(avFormatContext, 0)
找到媒体流的信息, 至此媒体文件的内容被封装到了AVFormatContext 结构体中了;
4、avFormatContext->nb_streams
开启遍历,nb_streams代表AVFormatContext结构中的流的个数,一般有视频流、音频流 和字幕流
5、avFormatContext->streams[index_stream]
获取到AVFormatContext结构体中的一个流
6、avcodec_find_decoder(avCodecParameters->codec_id)
通过编码器id找到对应的流的编解码器AVCodec
7、avcodec_alloc_context3(avCodec)
通过一个编解码器初始化出一个AVCodecContext
8、avcodec_parameters_to_context(avCodecContext, avCodecParameters)
往编解码器中设置流的参数
9、avcodec_open2(avCodecContext, avCodec, 0)
打开编解码器,此时这个流的编解码器和编解码器上下文才可以真正使用了。
第二步 、 音视频解码
音视频解码的逻辑是每个对应的流中(音频流 或者视频流)定义两个队列(未解码的AVPacket 和 已经解码的AVFrame)
1、av_packet_alloc()
初始化一个AVPacket,用来存放一个从编解码器上下文中读出来的一帧帧数据
2、av_read_frame(avFormatContext, avPacket)
从编解码器上下文中读一帧数据到AVPacket
3、packages.push(avPacket)
加入到队列
4、packages.pop(avPacket)
从队列中取出一个数据
5、AVFrame *avFrame = av_frame_alloc()
初始化一个AVFrame结构体
6、avcodec_receive_frame(avCodecContext, avFrame)
通过编解码器上下文中解码出一帧数据放到avFrame结构体中
7、frames.push(avFrame);
解码完成以后放入队列
其中123步在一个线程中执行,4567步在另一个线程中执行
第三步 视频的渲染
1、sws_getContext()
初始化一个SwsContext结构体,参数解释 第123个参数分别是被转换源的宽高和格式第456个参数转换后的宽高和格式 第7个参数是转换所使用的算法 剩下三个参数全部设置成NULL
2、av_image_alloc(dst_data, dst_linesize, pContext->width, pContext->height, AV_PIX_FMT_ARGB, 1)
给图像申请内存,dst_data代表图像通道、dst_linesize代表图像每个通道的内存对齐的步长,即一行的对齐内存的宽度。
3、frames.pop(frame)
从已解码的队列中取出一帧图像
4、sws_scale(sws_ctx, frame->data, frame->linesize, 0, pContext->height, dst_data, dst_linesize)
讲解码的原始数据转换成yuv数据
5、渲染到屏幕上
基于SurfaceView+ANativeWindow 渲染到屏幕
https://blog.youkuaiyun.com/byhook/article/details/84027283
1、ANativeWindow_fromSurface(env, surface)
获取到ANativeWindow 结构体
2、ANativeWindow_setBuffersGeometry(nativeWindow, width, height , WINDOW_FORMAT_RGBA_8888);
设置窗口属性
3、ANativeWindow_lock(nativeWindow, &windowBuffer, 0)
读取到窗口buffer数据
4、修改数据
// 填数据到buffer,其实就是修改数据
uint8_t * dst_data = static_cast<uint8_t *>(windowBuffer.bits);
int lineSize = windowBuffer.stride * 4; // RGBA
// 下面就是逐行Copy了
for (int i = 0; i < windowBuffer.height; ++i) {
// 一行Copy
memcpy(dst_data + i * lineSize, src_data + i * src_liinesize, lineSize);
}
第四步 音频的渲染
1、创建引擎并获取引擎接口
1、slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
2、(*engineObject) ->Realize(engineObject, SL_BOOLEAN_FALSE)
3、(*engineObject) ->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface)
2、创建混音器
1、(*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
2、(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE)
3、(*outputMixObject)->GetInterface(outputMixObject,SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb)
4、设置
const SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_DEFAULT;
(*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &settings);
3、创建播放器
1、配置输入声音信息
// 创建buffer缓冲类型的队列 2个队列
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2};
// pcm数据格式
// SL_DATAFORMAT_PCM:数据格式为pcm格式
// 2:双声道
// SL_SAMPLINGRATE_44_1:采样率为44100(44.1赫兹 应用最广的,兼容性最好的)
// SL_PCMSAMPLEFORMAT_FIXED_16:采样格式为16bit (16位)(2个字节)
// SL_PCMSAMPLEFORMAT_FIXED_16:数据大小为16bit (16位)(2个字节)
// SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT:左右声道(双声道) (双声道 立体声的效果)
// SL_BYTEORDER_LITTLEENDIAN:小端模式
SLDataFormat_PCM format_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 audioSrc = {&loc_bufq, &format_pcm};
2、配置音轨(输出
// 设置混音器
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
// 需要的接口 操作队列的接口
const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
3、创建播放器 CreateAudioPlayer(engineInterface, &bqPlayerObject, &audioSrc, &audioSnk, 1, ids, req);
4、初始化播放器 (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE)
5、获取播放器接口 (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay)
6、设置播放回调函数
// 获取播放器队列接口:SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
// 设置回调
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
7、设置播放器状态为播放状态 并手动激活回调函数
// 设置播放器状态为播放状态
(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
// 手动激活回调函数
bqPlayerCallback(bqPlayerBufferQueue, this);
8、回调函数编写(将pcm原始数据重采样)
1、定义分配重采样上下文
out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); // 通道数 AV_CH_LAYOUT_STEREO(双声道的意思)
out_sample_size = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
out_sample_rate = 44100; // 采样率
out_buffers_size = out_sample_rate * out_sample_size * out_channels;
out_buffers = static_cast<uint8_t *>(malloc(out_buffers_size));
memset(out_buffers, 0, out_buffers_size);
// swr_ctx = swr_alloc();
swr_ctx = swr_alloc_set_opts(0, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, out_sample_rate,
pContext->channel_layout, pContext->sample_fmt, pContext->sample_rate, 0 ,0);
swr_init(swr_ctx);
2、计算PCM数据大小
int AudioChannel::getPCM() {
int pcm_data_size = 0;
// PCM 在 frames 队列中
AVFrame * frame = 0;
while(isPlaying) {
int ret = frames.pop(frame);
// 如果停止播放,跳出循环, 出了循环,就要释放
if (!isPlaying) {
break;
}
if (!ret) {
continue;
}
// PCM 的处理逻辑
frame->data;
// 音频播放器的数据格式是我们在下面定义的(16位 双声道 ....)
// 而原始数据(是待播放的音频PCM数据)
// 所以,上面的两句话,无法统一,一个是(自己定义的16位 双声道 ..) 一个是原始数据,为了解决上面的问题,就需要重采样。
// 开始重采样
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);
ret = swr_convert(swr_ctx, &out_buffers, dst_nb_samples, (const uint8_t **) frame->data, frame->nb_samples);
if (ret < 0) {
fprintf(stderr, "Error while converting\n");
}
// pcm_data_size = ret * out_sample_size; // 每个声道的数据
pcm_data_size = ret * out_sample_size * out_channels;
break;
} // end while
releaseAVFrame(&frame); // 渲染完了,frame没用了,释放掉
return pcm_data_size;
}
3、播放数据(*bq)->Enqueue(bq, audioChannel->out_buffers , pcmSize);