库
- libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构
- 和读取音视频帧等功能;
- libavcodec:用于各种类型声音/图像编解码;
- libavutil:包含一些公共的工具函数;
- libswscale:用于视频场景比例缩放、色彩映射转换;
- libavfilter:在 FFmpeg 中有多种多样的滤镜,你可以把他们当成一个个小工具,专门用于处理视频和音频数据,以便实现一定的目的。如 overlay 这个滤镜,可以将一个图画覆盖到另一个图画上;transport 这个滤镜可以将图画做旋转等等。
- libswresample: 对音频的采样频率转换,如将采样率从44.1K转换到48K,注意从高采样频率到低采样频率的音频转换是一个有损的过程。
Prepare流程
1、 申请formatContext上下文
formatContext = avformat_alloc_context();
// 字典(键值对)
AVDictionary *dictionary = nullptr;
//设置超时(5秒)
av_dict_set(&dictionary, "timeout", "5000000", 0); // 单位微妙
2、 打开文件
av_register_all();
int r = avformat_open_input(&formatContext, data_source, nullptr, &dictionary);
3、 查找媒体中的音视频流的信息
r = avformat_find_stream_info(formatContext, nullptr);
4、进行流解码操作并获取解码器,将参数给解码器
//TODO 第三步 根据流的信息获取流
for (int stream_index = 0; stream_index < formatContext->nb_streams; ++stream_index) {
// TODO 第四步 获取媒体流 音 视
AVStream *stream = formatContext->streams[stream_index];
//TODO 第五步 从上面的流中 获取编码的参数
AVCodecParameters *parameters = stream->codecpar;
//TODO 第六步 根据上面的参数获取解码器
AVCodec *codec = avcodec_find_decoder(parameters->codec_id);
//error ......
//TODO 第七步 编解码器的上下文 没有内容
codecContext = avcodec_alloc_context3(codec);
//error ......
//TODO 第八步 将参数给avCodecContext
r = avcodec_parameters_to_context(codecContext,parameters);
//TODO 第九步打开解码器
r = avcodec_open2(codecContext,codec, nullptr);
5、分发流处理
if (parameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO){
// 虽然是视频类型,但是只有一帧封面
if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) {
continue;
}
//视频
// 虽然是视频类型,但是只有一帧封面
if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) {
continue;
}
video_channel = new VideoChannel(stream_index, codecContext, time_base, fps);
} else {
audio_channel = new AudioChannel(stream_index, codecContext,time_base);
}
start流程
1、 解决帧之间的延迟问题
if (video_channel && video_channel->packets.size() >100){
av_usleep(10 * 1000); //微秒
continue;
}
if (audio_channel && audio_channel->packets.size() >100){
av_usleep(10 * 1000); //微秒
continue;
}
2、 将AVPacket包入队
AVPacket *packet = av_packet_alloc();
int ret = av_read_frame(formatContext,packet);
if (!ret){
//音频
if (audio_channel && audio_channel->stream_index == packet->stream_index){
audio_channel->packets.insertToQueue(packet);
//视频
} else if (video_channel && video_channel->stream_index == packet->stream_index){
video_channel->packets.insertToQueue(packet);
}
} else if (ret == AVERROR_EOF){//end of file 文件末尾
//表示读完了 并没有播放完
//TODO 内存泄漏点2
if (video_channel->frames.empty() && audio_channel->frames.empty()){
if (helper){
helper->onFinish(THREAD_CHILD);
}
break;//队列的数据被播放完成 再退出
}
} else{
break; // 出现了错误
}
解析出原始包流程
//视频
int ret = packets.getQueueAndDel(pkt);
if (!isWorking){ //如果关闭播放 就跳出去
break;
}
if (!ret) { // ret == 0
continue; // 哪怕是没有成功,也要继续(假设:你生产太慢(压缩包加入队列),我消费就等一下你)
}
//最新版本FFmpeg 差别 1、发送pkt给缓冲区 2、从缓冲区拿原始包
ret = avcodec_send_packet(codecContext,pkt);
//由于ffmpeg缓存了一份 可以大胆释放
//releaseAVPacket(&pkt);
if (ret){
break; //出现错误了
}
//下面是获取帧
AVFrame *frame = av_frame_alloc();
ret = avcodec_receive_frame(codecContext,frame);
if (ret == AVERROR(EAGAIN)){ // b帧参考前面成功 后面却失败了 可能是p帧没出来 需要再取数据
continue;
} else if (ret != 0){
//TODO 内存泄漏点4 解码出错的时候
if(frame){
releaseAVFrame(&frame);
}
break; // 错误了
}
//拿到了原始包
frames.insertToQueue(frame);
// TODO 内存泄漏点5
//安心释放packet pkt有默认FFmpeg帮你开辟的堆区
av_packet_unref(pkt);// 减引用 自动释放 释放成员的空间
releaseAVPacket(&pkt);//释放本身的空间
//音频
ret = avcodec_send_packet(codecContext,packet);
releaseAVPacket(&packet);
if (ret){
break;
}
AVFrame *frame = av_frame_alloc();
ret = avcodec_receive_frame(codecContext,frame);
if (ret == AVERROR(EAGAIN)){
continue;
} else if (ret != 0){
//TODO 内存泄漏点4 解码出错的时候
if(frame){
releaseAVFrame(&frame);
}
break;
}
frames.insertToQueue(frame);
// TODO 内存泄漏点5
//安心释放packet pkt有默认FFmpeg帮你开辟的堆区
if (packet){
av_packet_unref(packet);// 减引用 自动释放 释放成员的空间
releaseAVPacket(&packet);//释放本身的空间
}
视频play流程
void VideoChannel::video_play() {
// SWS_FAST_BILINEAR == 很快 可能会模糊
// SWS_BILINEAR 适中算法
AVFrame *frame = 0;
uint8_t *dst_data[4]; // RGBA
int dst_linesize[4]; // RGBA
// 原始包(YUV数据) ---->[libswscale] Android屏幕(RGBA数据)
//给 dst_data 申请内存 width * height * 4 xxxx
av_image_alloc(dst_data, dst_linesize,
codecContext->width, codecContext->height, AV_PIX_FMT_RGBA, 1);
// yuv -> rgba
LOGD("width:%d,height:%d",codecContext->width,codecContext->height)
SwsContext *sws_ctx = sws_getContext(
// 下面是输入环节
codecContext->width,
codecContext->height,
codecContext->pix_fmt, // 自动获取 xxx.mp4 的像素格式 AV_PIX_FMT_YUV420P // 写死的
// 下面是输出环节
codecContext->width,
codecContext->height,
AV_PIX_FMT_RGBA,
SWS_BILINEAR,//像素算法适中的 线性差值
NULL,
NULL,
NULL
);
while (isWorking){
if (!isPlay){
av_usleep(10*1000);
continue;
}
int ret = frames.getQueueAndDel(frame);
if (!isWorking){
break; //关闭了播放器 跳出 并释放releaseFrame
}
if (!ret){
continue; //原始包产生太慢 需要消费等待下
}
//格式转换 yuv-->rgba
sws_scale(sws_ctx,
//输入环节 yuv数据
frame->data,
frame->linesize,//大小 一行一行渲染
0,
codecContext->height,
//下面是输出环节 成果:RGBA数据
dst_data,
dst_linesize
);
//TODO 音视频同步 3 根据fps来进行休眠 FPS间隔时间加入
double extra_delay = frame->repeat_pict / (2*fps); //额外延时时间
double fps_delay = 1.0 / fps; // 0.04 (每一帧的延时时间)
double video_delay = extra_delay + fps_delay;
//根据视频来处理的 没有和音频相关
//av_usleep((extra_delay+fps_delay) * 1000000);
double video_time = frame->best_effort_timestamp * av_q2d(time_base);
double audio_time = audioChannel->audio_time;
//判断两个时间插值 你追我赶
double time_diff = video_time - audio_time;
if (time_diff > 0){
//视频 睡眠
if (time_diff > 1){//差距较大 稍微睡一下
av_usleep(video_delay * 2 * 1000000);
} else { //差距不大 当前时间延迟时间 + 音视频差值
av_usleep((video_delay + time_diff) * 1000000);
}
}
if (time_diff < 0){
//视频时间 < 音频时间 丢包
//不能丢I帧
//可以在frame 和 packets中去丢
//经验值 0.05
if (fabs(time_diff) <= 0.05){ //对浮点数取绝对值
frames.sync();
continue;
}
} else {
LOGI("百分百同步了")
}
//ANativeWindows 渲染工作 surfaceview --- ANativeWindows
// 渲染一帧图片 由于拿不到surfaceview 只能回调给native-lib
renderCallback(dst_data[0],
codecContext->width,
codecContext->height,
dst_linesize[0]
);
//渲染完 数据没用了 释放
//TODO 内存泄漏点6
// releaseAVFrame(&frame);
av_frame_unref(frame); //释放frame中的成员
releaseAVFrame(&frame); //释放frame
}
//releaseAVFrame(&frame); //出现了意外 多要释放
//TODO 内存泄漏点6
// releaseAVFrame(&frame);
av_frame_unref(frame); //释放frame中的成员
releaseAVFrame(&frame); //释放frame
isWorking=0;
av_free(&dst_data[0]);
//free(sws_ctx); 不用c的释放 需要用ffmpeg的释放
sws_freeContext(sws_ctx);
}
音频play流程
void AudioChannel::audio_play() {
SLresult result ;// 用于接收执行成功的返回值
/**
* TODO 1、创建引擎并获取引擎接口
*/
//创建引擎对象
result = slCreateEngine(&engineObject,0,0,0,0,0);
if (SL_RESULT_SUCCESS != result){
LOGE("创建音频引擎 slCreateEngine失败")
return;
}
//初始化引擎
//SL_BOOLEAN_FALSE : 延时等待创建成功
result = (*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result){
LOGE("创建音频引擎 Realize失败")
return;
}
//获取引擎接口
result = (*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface);
if (SL_RESULT_SUCCESS != result){
LOGE("获取引擎接口 GetInterface失败")
return;
}
if (engineInterface){
LOGD("AUDIO SUCCESS")
} else {
LOGD("AUDIO FAILURE")
}
/**
* TODO 2、混音器
*/
//0:环境特效,混响特效 ...
result = (*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,0,0);
if (SL_RESULT_SUCCESS != result){
LOGE("初始化混音器 CreateOutputMix失败")
return;
}
//初始化混音器
result = (*outputMixObject)->Realize(outputMixObject,SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result){
LOGE("初始化混音器 Realize失败")
return;
}
// 不启用混响可以不用获取混音器接口 【声音的效果】
// 获得混音器接口
/*
result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
&outputMixEnvironmentalReverb);
if (SL_RESULT_SUCCESS == result) {
// 设置混响 : 默认。
SL_I3DL2_ENVIRONMENT_PRESET_ROOM: 室内
SL_I3DL2_ENVIRONMENT_PRESET_AUDITORIUM : 礼堂 等
const SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_DEFAULT;
(*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
outputMixEnvironmentalReverb, &settings);
}
*/
/**
* TODO 3、创建播放器
*/
/**
* TODO 3.创建播放器
*/
// 创建buffer缓存类型的队列 2的队列大小
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
10};
// pcm数据格式 == PCM是不能直接播放,mp3可以直接播放(参数集),人家不知道PCM的参数
// SL_DATAFORMAT_PCM:数据格式为pcm格式
// 2:双声道
// SL_SAMPLINGRATE_44_1:采样率为44100
// SL_PCMSAMPLEFORMAT_FIXED_16:采样格式为16bit
// SL_PCMSAMPLEFORMAT_FIXED_16:数据大小为16bit
// SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT:左右声道(双声道)
// SL_BYTEORDER_LITTLEENDIAN:小端模式
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, // PCM数据格式
2, // 声道数
SL_SAMPLINGRATE_44_1, // 采样率(每秒44100个点)
SL_PCMSAMPLEFORMAT_FIXED_16, // 每秒采样样本 存放大小 16bit
SL_PCMSAMPLEFORMAT_FIXED_16, // 每个样本位数 16bit
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, // 前左声道 前右声道
SL_BYTEORDER_LITTLEENDIAN}; // 字节序(小端) 例如:int类型四个字节(到底是 高位在前 还是 低位在前 的排序方式,一般我们都是小端)
// 数据源 将上述配置信息放到这个数据源中
// audioSrc最终配置音频信息的成果,给后面代码使用
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
// audioSrc 给后面代码用的
// 独立声卡:24bit 集成声卡16bit
// 3.2 配置音轨(输出)
// 设置混音器
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; // SL_DATALOCATOR_OUTPUTMIX:输出混音器类型
SLDataSink audioSnk = {&loc_outmix, NULL}; // outmix最终混音器的成果,给后面代码使用
// 需要的接口 操作队列的接口
const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
// 3.3 创建播放器 SLObjectItf bqPlayerObject
result = (*engineInterface)->CreateAudioPlayer(engineInterface, // 参数1:引擎接口
&bqPlayerObject, // 参数2:播放器
&audioSrc, // 参数3:音频配置信息
&audioSnk, // 参数4:混音器
// TODO 下面代码都是 打开队列的工作
1, // 参数5:开放的参数的个数
ids, // 参数6:代表我们需要 Buff
req // 参数7:代表我们上面的Buff 需要开放出去
);
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);
LOGI("6、手动激活回调函数 Success");
}
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void * args) {
auto *audio_channel = static_cast<AudioChannel *>(args);
int pcm_size = audio_channel->getPCM();
// 添加数据到缓冲区里面去
(*bq)->Enqueue(
bq, // 传递自己,为什么(因为没有this,为什么没有this,因为不是C++对象,所以需要传递自己) JNI讲过了
audio_channel->out_buffers, // PCM数据
pcm_size); // PCM数据对应的大小,缓冲区大小怎么定义?(复杂)
}