기사 디렉토리
간단한 설명
CPU 스케줄링 및 디코딩 효율과 같은 요소가 동기화될 수 없기 때문에 비디오와 오디오는 서로 다른 스레드에서 재생되므로 인위적인 동기화가 필요합니다. 벤치마크로서의 비디오, 벤치마크로서의 오디오 및 타사 벤치마크의 세 가지 유형이 있습니다. 생물학적인 이유 때문에 사람들은 소리에 더 민감하기 때문에 오디오를 조정하는 것이 불편하고 일반적으로 오디오를 기반으로 합니다.
DTS : Decoding Time Stamp, decoding timestamp는 디코더 패킷의 디코딩 순서를 알려줍니다.
PTS : 패킷에서 디코딩된 데이터의 표시 순서를 나타내는 타임스탬프를 표시하는 Presentation Time Stamp.
데이터는 위의 두 가지와 동일하게 오디오에 순차적으로 저장됩니다. 영상에서는 일부 중간 프레임이 전면 프레임과 후면 프레임에 의존하기 때문에 종속 후면 프레임이 중간 프레임 앞에 저장되어 위 두 프레임의 차이가 발생합니다.
time_base : 타임 베이스, 타임 스탬프와 타임 초의 변환 단위, AVRational 구조는 분자와 분모를 저장합니다. double을 직접 저장하지 않는 이유는 av_q2d 함수를 통해 변환해야 하는데 아마도 정확도 문제 때문일 것입니다.
/**
* Rational number (pair of numerator and denominator).
*/
typedef struct AVRational{
int num; ///< Numerator
int den; ///< Denominator
} AVRational;
/**
* This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented.
*
* decoding: set by libavformat
* encoding: May be set by the caller before avformat_write_header() to
* provide a hint to the muxer about the desired timebase. In
* avformat_write_header(), the muxer will overwrite this field
* with the timebase that will actually be used for the timestamps
* written into the file (which may or may not be related to the
* user-provided one, depending on the format).
*/
AVRational time_base;
예를 들어 특정 프레임의 표시 시간은 다음 공식으로 계산할 수 있습니다(결과 videoClock 단위는 초).
videoClock = pts * av_q2d(time_base);
오디오 시계
먼저 변수 정의
AVRational audioTimeBase;
double audioClock;//音频时钟
그런 다음 오디오 스트림을 얻을 때 오디오 시간축을 얻습니다.
if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_index = i;
audioTimeBase = avFormatContext->streams[i]->time_base;
}
마지막으로 getPcm 함수에서 오디오 시계를 업데이트합니다.
if (audioFrame->pts != AV_NOPTS_VALUE) {
//这一帧的起始时间
audioClock = audioFrame->pts * av_q2d(audioTimeBase);
//这一帧数据的时间
double time = size / ((double) 44100 * 2 * 2);
//最终音频时钟
audioClock = time + audioClock;
}
비디오 동기화 오디오
먼저 변수 정의
AVRational videoTimeBase;
double videoClock;//视频时钟
위와 같이 비디오 스트림을 가져올 때 비디오 시간축을 가져옵니다.
if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_index = i;
videoTimeBase = avFormatContext->streams[i]->time_base;
}
비디오 프레임을 가져오는 루프 함수에서 루프 외부의 변수를 정의합니다.
double last_play //上一帧的播放时间
, play //当前帧的播放时间
, last_delay // 上一次播放视频的两帧视频间隔时间
, delay //线程休眠时间
, diff //音频帧与视频帧相差时间
, sync_threshold //合理的范围
, pts
, decodeStartTime //每一帧解码开始时间
, frame_time_stamp = av_q2d(videoTimeBase); //时间戳的实际时间单位
휴면 시간을 계산하고 패킷을 가져오는 주기에서 동기화합니다. 주로 각 프레임의 표시 시간을 연장하거나 단축하여
decodeStartTime = av_gettime() / 1000000.0;
AVPacket *packet = videoPacketQueue.front();
videoPacketQueue.pop();
avcodec_send_packet(avCodecContext, packet);
AVFrame *frame = av_frame_alloc();
if (!avcodec_receive_frame(avCodecContext, frame)) {
if ((pts = frame->best_effort_timestamp) == AV_NOPTS_VALUE) {
pts = videoClock;
}
play = pts * frame_time_stamp;
videoClock =
play + (frame->repeat_pict * 0.5 * frame_time_stamp + frame_time_stamp);
delay = play - last_play;
diff = videoClock - audioClock;
sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
if (fabs(diff) < 10) {
if (diff <= -sync_threshold) {
delay = FFMAX(0.01, delay + diff);
} else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) {
delay = delay + diff;
} else if (diff >= sync_threshold && delay) {
delay = 2 * delay;
}
}
if (delay <= 0 || delay > 1) {
delay = last_delay;
}
last_delay = delay;
//减去解码消耗时间
delay = delay + (decodeStartTime - av_gettime() / 1000000.0);
if (delay < 0) {
delay = 0;
}
last_play = play;
비디오 프레임을 던지고 마침내 잠
ANativeWindow_unlockAndPost(nativeWindow);
if (delay > 0.001) {
av_usleep(delay * 1000000);
}
여전히 개선해야 할 세부 사항이 있지만 일반적으로 이와 같습니다.