NDK FFmpeg音视频播放器五

NDK前期基础知识终于学完了,现在开始进入项目实战学习,通过FFmpeg实现一个简单的音视频播放器。

NDK FFmpeg音视频播放器五_第1张图片

音视频一二三四节已经实现了音视频播放和解决内存泄漏问题,本节主要是完成音视频同步问题。

本节内容如下:

1.音视频同步画图分析。
2.fps概念与时间基Timebase概念。
3.音视频同步编码实现。

用到的ffmpeg、rtmp等库资源:
https://wwgl.lanzout.com/iN21C0qiiija

一、音视频同步画图分析

1)以音频播放时间为准,做视频同步(95%使用该方式,简单直接,人类对音频是敏感的)
2)以视频播放时间为准,做音频同步
3)自己算出一个播放时间为准,维护音频,维护视频,维护自己算法的时间,做同步(不推荐)

NDK FFmpeg音视频播放器五_第2张图片

本节主要使用方式1,实现逻辑:
1.获取音频播放的时间搓
2.获取视频播放的时间搓
3.如果视频播放的时间搓 > 音频播放的时间搓,视频播放快,需要等待音频播放,视频播放休眠
4.如果视频播放的时间搓 < 音频播放的时间搓,视频播放慢,需要追赶音频播放,视频播放丢包

二、fps概念与时间基Timebase概念

1)fps是视频通道独有的,fps:一秒钟多少帧;
2)时间基TimeBase:
在FFmpeg里面播放时间有自己的单位(时间基TimeBase),可以理解为:例如:(fps25 一秒钟25帧, 那么每一帧==25分之1,而25分之1就是时间基概念),需要将TimeBase转换为时间戳。

三、音视频同步编码实现

1.音视频同步
/**
 * 1.out_buffers 给予数据
 * 2.out_buffers 给予数据的大小计算工作
 * @return  大小还要计算,因为我们还要做重采样工作,重采样之后,大小不同了
 */
int AudioChannel::getPCM() {
    LOGI("AudioChannel::getPCM");
    int pcm_data_size = 0;
    // 从frames队列中,获取PCM数据,frame->data == PCM数据(待 重采样 32bit)
    AVFrame *frame = 0;
    while (isPlaying) {
        //...

        /**
         * TODO 1.音视频同步
         * 同步方式主要有三种:
         * 1)以音频播放时间为准,做视频同步(95%使用该方式,简单直接,人类对音频是敏感的)
         * 2)以视频播放时间为准,做音频同步
         * 3)自己算出一个播放时间为准,维护音频,维护视频,维护自己算法的时间,做同步(不推荐)
         *
         * 本节主要使用方式1):
         * 1.获取音频播放的时间搓
         * 2.获取视频播放的时间搓
         * 3.如果视频播放的时间搓 > 音频播放的时间搓,视频播放快,需要等待音频播放,视频播放休眠
         * 4.如果视频播放的时间搓 < 音频播放的时间搓,视频播放慢,需要追赶音频播放,视频播放丢包
         */
        /**
         * 获取音频播放的时间搓
         * 在FFmpeg里面播放时间有自己的单位(时间基TimeBase),
         * 时间基TimeBase理解:例如:(fps25 一秒钟25帧, 那么每一帧==25分之1,而25分之1就是时间基概念)
         * 需要将TimeBase转换为时间戳audio_time,TimeBase在解封装的时候获取
         */
        audio_time = frame->best_effort_timestamp * av_q2d(time_base); // 必须这样计算后,才能拿到真正的时间搓
        break;
    } // while end
    return pcm_data_size;
}
1.1音视频同步
// 1.1音视频同步,音频播放时间戳
double audio_time;
2.音视频同步
// TODO 2音视频同步,获取时间基
AVRational time_base = stream->time_base;
2.1音视频同步
// 2.1音视频同步(AudioChannel VideoChannel 都需要时间基)单位而已
AVRational time_base; 
2.2音视频同步
// 2.2音视频同步 (视频独有的 fps值 一秒钟多少帧)
AVRational fps_rational = stream->avg_frame_rate;
int fps = av_q2d(fps_rational);
// 视频,保存视频流下标,用于后面判断获取的压缩包是否是视频压缩包
video_channel = new VideoChannel(i, codec_context, time_base, fps);
2.3音视频同步
// 2.3音视频同步
int fps; // fps是视频通道独有的,fps(一秒钟多少帧)
3.音视频同步
/**
 * TODO 3.音视频同步 (根据fps来休眠) FPS间隔时间加入,有延时感觉,顺滑
 * 公式:extra_delay = repeat_pict / (2*fps)
 * extra_delay:0.0400000
 * 0.04是这一帧的真实时间加上延迟时间
 */
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; // extra_delay大概率获取不到,当前帧的延时时间=0.040000
// 计算视频的播放时间搓和音频的播放时间搓
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;
LOGI("VideoChannel::video_play() time_diff %lf",time_diff);
if (time_diff > 0) {
	// 视频时间 > 音频时间:要等音频,所以控制视频播放慢一点(等音频) 【睡眠】
	if (time_diff > 1) {
		// 如果音频与视频差别很大,可能是拖动条等特殊场景,不能睡眠那么久,否则是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
		LOGI("VideoChannel::video_play() av_usleep");
	}
} else if (time_diff < 0) {
	// 视频时间 < 音频时间 要追音频,所以控制视频播放快一点(追音频) 【丢包】
	// 丢帧:不能睡意丢,I帧是绝对不能丢
	// 丢包:在frames 和 packets 中的队列

	// 绝对值在0.05范围内进行丢包处理
	if (fabs(time_diff) <= 0.05) { // fabs对负数的操作(对浮点数取绝对值)
		// 多线程(安全 同步丢包)
		frames.sync();
		continue; // 丢完取下一个包
	}
}
3.1音视频同步
// 3.1音视频同步
AudioChannel *audio_channel = 0;
3.2音视频同步
// 3.2音视频同步
video_channel->setAudioChannel(audio_channel);
4.音视频同步
// TODO 4.音视频同步
SyncCallback syncCallback;

typedef void (*SyncCallback)(queue &); // 函数指针定义 做回调 让外界完成丢包动作

/**
 * 设置此函数指针的回调,让外界去丢包
 * @param syncCallback
 */
void setSyncCallback(SyncCallback syncCallback) {
	this->syncCallback = syncCallback;
}

/**
 * 同步操作 丢包
 */
void sync() {
	pthread_mutex_lock(&mutex);
	syncCallback(queue); // 函数指针 具体丢包动作,让外界完成
	pthread_mutex_unlock(&mutex);
}
4.1音视频同步
// 4.1音视频同步,设置回调函数
frames.setSyncCallback(dropAVFrame);
packets.setSyncCallback(dropAVPacket);

至此,音视频同步问题已解决,下一接将完成音视频播放拖动条功能。。。

你可能感兴趣的:(NDK,ffmpeg,音视频,NDK)