오디오 및 비디오 시리즈 5 오디오 및 비디오 동기화

간단한 설명

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);
                }

여전히 개선해야 할 세부 사항이 있지만 일반적으로 이와 같습니다.

소스 코드

소스 코드

추천

출처blog.csdn.net/Welcome_Word/article/details/120051643