FFmpeg之音画同步与资源释放

前面使用FFmpeg完成了视频和音频的播放,但是项目跑起来后发现视频和画面并不一致,也就是说我们从队列中取出数据进行播放时是没有对画面和音频时间进行同步处理的,所以出现了问题。同时播放完成以及错误或者退出时要进行相关的资源释放工作。

音画同步的处理

保证画面和音频同步其实很简单,就是尽量保证二者进行输出播放时的时刻尽最大可能进行接近处理。

也就是说也以某个为基准去同步另一个,那么我们应该以哪个为基准呢,一般都是以音频为基准的,因为人对声音的敏感度要大于画面的敏感度。

在视频播放中进行时间的同步处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
void VideoChannel::video_play() {

AVFrame *frame = nullptr;
uint8_t *dest_data[4];//RGBA 4字节
int dst_lineSize[4];//RGBA

av_image_alloc(dest_data, dst_lineSize,
codecContext->width, codecContext->height,
AV_PIX_FMT_RGBA, 1);

//YUV -> RGBA
SwsContext *swsContext = sws_getContext(
//输入
codecContext->width,
codecContext->height,
codecContext->pix_fmt,
//输出
codecContext->width,
codecContext->height,
AV_PIX_FMT_RGBA,
SWS_BILINEAR,nullptr, nullptr, nullptr);
while (isPlaying){
int res = frames.getQueueAndDel(frame);
if(!isPlaying){
releaseAVFrame(&frame);
break;//如果关闭了播放,跳出循环
}
if(!res){
continue;//有可能生产者队列填充数据过慢,需要等一等
}

sws_scale(swsContext,
//输入
frame->data, frame->linesize,
0, codecContext->height,
//输出
dest_data, dst_lineSize);

// TODO 音视频同步 3(根据fps来休眠) FPS间隔时间加入(我的视频 默默的播放,不要看起来怪怪的) == 要有延时感觉
// 0.04是这一帧的真实时间加上延迟时间吧
// 公式:extra_delay = repeat_pict / (2*fps)
// 经验值 extra_delay:0.0400000
double extra_delay = frame->repeat_pict / (2 * fps); // 在之前的编码时,加入的额外延时时间取出来(可能获取不到)
double fps_delay = 1.0 / fps; // 根据fps得到延时时间(fps25 == 每秒25帧,计算每一帧的延时时间,0.040000)
double real_delay = fps_delay + extra_delay; // 当前帧的延时时间 0.040000

// fps间隔时间后的效果,任何播放器,都会有
// 为什么不能用:根据是 视频的 fps延时在处理,和音频还没有任何关系
// av_usleep(real_delay * 1000000);

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 下面是音视频同步
double video_time = frame->best_effort_timestamp * av_q2d(time_base);
double audio_time = audio_channel->audio_time;

// 判断两个时间差值,一个快一个慢(快的等慢的,慢的快点追) == 你追我赶
double time_diff = video_time - audio_time;
if(time_diff > 0){
//视频较快
// 视频时间 > 音频时间: 要等音频,所以控制视频播放慢一点(等音频) 【睡眠】
if (time_diff > 1)
{ // 说明:音频预视频插件很大,TODO 拖动条 特色场景 音频 和 视频 差值很大,我不能睡眠那么久,否则是大Bug
// av_usleep((real_delay + time_diff) * 1000000);

// 如果 音频 和 视频 差值很大,我不会睡很久,我就是稍微睡一下
av_usleep((real_delay * 2) * 1000000);
}
else
{ // 说明:0~1之间:音频与视频差距不大,所以可以那(当前帧实际延时时间 + 音视频差值)
av_usleep((real_delay + time_diff) * 1000000); // 单位是微妙:所以 * 1000000
}
}else if(time_diff < 0){
//音频较快
// 视频时间 < 音频时间: 要追音频,所以控制视频播放快一点(追音频) 【丢包】
// 丢帧:不能睡意丢,I帧是绝对不能丢
// 丢包:在frames 和 packets 中的队列

// 经验值 0.05
// -0.234454 fabs == 0.234454
if(fabs(time_diff) <= 0.05){
// 多线程(安全 同步丢包)
frames.sync();
continue; // 丢完取下一个包
}
}else{
// 百分百同步,这个基本上很难做的
LOGI("百分百同步了");
}

//ANativeWindow渲染
if(renderCallback && isPlaying)
renderCallback(dest_data[0], codecContext->width, codecContext->height, dst_lineSize[0]);
releaseAVFrame(&frame);//释放原始包,因为已经被渲染过了,没用了
}
releaseAVFrame(&frame);
isPlaying = false;
av_freep(dest_data[0]);
sws_freeContext(swsContext);
}

这样也就尽量保证了画面和视频的同步输出播放。

资源的释放

  1. AVFormatContext的释放

    1
    2
    3
    4
    5
    if(formatContext){
    avformat_close_input(&formatContext);
    avformat_free_context(formatContext);
    formatContext = nullptr;
    }
  2. Audio与Video的处理释放

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void VideoChannel::stop(){
    LOGD("VideoChannel prepare stop");
    isPlaying = false;
    pthread_join(pid_video_decode, nullptr);
    pthread_join(pid_video_play, nullptr);

    packets.setWork(0);
    frames.setWork(0);
    packets.clear();
    frames.clear();

    LOGD("VideoChannel stop");
    }
  3. AVPacket与AVFrame的释放

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    static void releaseAVPacket(AVPacket ** p){
    if(p){
    av_packet_unref(*p);
    av_packet_free(p);
    *p = nullptr;
    }
    }

    static void releaseAVFrame(AVFrame ** f){
    if(f){
    av_frame_unref(*f);
    av_frame_free(f);
    *f = nullptr;
    }
    }
  4. ANativeWindow的释放

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    extern "C"
    JNIEXPORT void JNICALL
    Java_cn_jiajunhui_lib_jjhplayer_JJHPlayer_nativeRelease(JNIEnv
    * env,
    jobject thiz
    ) {
    LOGD("native-lib prepare release");
    pthread_mutex_lock(&mutex);

    // 先释放之前的显示窗口
    if (window) {
    ANativeWindow_release(window);
    window = nullptr;
    }

    pthread_mutex_unlock(&mutex);

    if(player)
    player->release();
    DELETE(player);
    DELETE(vm);
    LOGD("native-lib release");
    }

源码地址:https://github.com/jiajunhui/ffmpeg-jjhplayer