c++ - 正确计算PTS和DTS同步音视频ffmpeg C++

标签 c++ audio video ffmpeg

我正在尝试将 H264 编码数据和 G711 PCM 数据复用到 mov 多媒体容器中。我正在从编码数据创建 AVPacket,最初视频/音频帧的 PTS 和 DTS 值等同于 AV_NOPTS_VALUE。所以我使用当前时间信息计算了 DTS。我的代码-

bool AudioVideoRecorder::WriteVideo(const unsigned char *pData, size_t iDataSize, bool const bIFrame) {
    .....................................
    .....................................
    .....................................
    AVPacket pkt = {0};
    av_init_packet(&pkt);
    int64_t dts = av_gettime();
    dts = av_rescale_q(dts, (AVRational){1, 1000000}, m_pVideoStream->time_base);
    int duration = 90000 / VIDEO_FRAME_RATE;
    if(m_prevVideoDts > 0LL) {
        duration = dts - m_prevVideoDts;
    }
    m_prevVideoDts = dts;

    pkt.pts = AV_NOPTS_VALUE;
    pkt.dts = m_currVideoDts;
    m_currVideoDts += duration;
    pkt.duration = duration;
    if(bIFrame) {
        pkt.flags |= AV_PKT_FLAG_KEY;
    }
    pkt.stream_index = m_pVideoStream->index;
    pkt.data = (uint8_t*) pData;
    pkt.size = iDataSize;

    int ret = av_interleaved_write_frame(m_pFormatCtx, &pkt);

    if(ret < 0) {
        LogErr("Writing video frame failed.");
        return false;
    }

    Log("Writing video frame done.");

    av_free_packet(&pkt);
    return true;
}

bool AudioVideoRecorder::WriteAudio(const unsigned char *pEncodedData, size_t iDataSize) {
    .................................
    .................................
    .................................
    AVPacket pkt = {0};
    av_init_packet(&pkt);

    int64_t dts = av_gettime();
    dts = av_rescale_q(dts, (AVRational){1, 1000000}, (AVRational){1, 90000});
    int duration = AUDIO_STREAM_DURATION; // 20
    if(m_prevAudioDts > 0LL) {
        duration = dts - m_prevAudioDts;
    }
    m_prevAudioDts = dts;
    pkt.pts = AV_NOPTS_VALUE;
    pkt.dts = m_currAudioDts;
    m_currAudioDts += duration;
    pkt.duration = duration;

    pkt.stream_index = m_pAudioStream->index;
    pkt.flags |= AV_PKT_FLAG_KEY;
    pkt.data = (uint8_t*) pEncodedData;
    pkt.size = iDataSize;

    int ret = av_interleaved_write_frame(m_pFormatCtx, &pkt);
    if(ret < 0) {
        LogErr("Writing audio frame failed: %d", ret);
        return false;
    }

    Log("Writing audio frame done.");

    av_free_packet(&pkt);
    return true;
}

然后我像这样添加了流 -

AVStream* AudioVideoRecorder::AddMediaStream(enum AVCodecID codecID) {
    ................................
    .................................   
    pStream = avformat_new_stream(m_pFormatCtx, codec);
    if (!pStream) {
        LogErr("Could not allocate stream.");
        return NULL;
    }
    pStream->id = m_pFormatCtx->nb_streams - 1;
    pCodecCtx = pStream->codec;
    pCodecCtx->codec_id = codecID;

    switch(codec->type) {
    case AVMEDIA_TYPE_VIDEO:
        pCodecCtx->bit_rate = VIDEO_BIT_RATE;
        pCodecCtx->width = PICTURE_WIDTH;
        pCodecCtx->height = PICTURE_HEIGHT;
        pStream->time_base = (AVRational){1, 90000};
        pStream->avg_frame_rate = (AVRational){90000, 1};
        pStream->r_frame_rate = (AVRational){90000, 1}; // though the frame rate is variable and around 15 fps
        pCodecCtx->pix_fmt = STREAM_PIX_FMT;
        m_pVideoStream = pStream;
        break;

    case AVMEDIA_TYPE_AUDIO:
        pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
        pCodecCtx->bit_rate = AUDIO_BIT_RATE;
        pCodecCtx->sample_rate = AUDIO_SAMPLE_RATE;
        pCodecCtx->channels = 1;
        m_pAudioStream = pStream;
        break;

    default:
        break;
    }

    /* Some formats want stream headers to be separate. */
    if (m_pOutputFmt->flags & AVFMT_GLOBALHEADER)
        m_pFormatCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;

    return pStream;
}

这个计算有几个问题:

  1. 随着时间的推移,视频越来越落后于音频。

  2. 假设,最近收到一个音频帧 (WriteAudio(..)),大约 3 秒,那么迟到的帧应该以 3 秒的延迟开始播放,但事实并非如此。延迟帧与前一帧连续播放。

  3. 有时我录制了大约 40 秒,但文件持续时间很像 2 分钟,但音频/视频只播放了 40 秒之类的片刻,文件的其余部分什么都不包含,搜索栏在 40 秒后立即跳转到 en秒(在 VLC 中测试)。

编辑:

根据Ronald S. Bultje的建议,我的理解是:

m_pAudioStream->time_base = (AVRational){1, 9000}; // actually no need to set as 9000 is already default value for audio as you said
m_pVideoStream->time_base = (AVRational){1, 9000};

应该设置为现在音频和视频流都采用相同的时基单位。

对于视频:

...................
...................

int64_t dts = av_gettime(); // get current time in microseconds
dts *= 9000; 
dts /= 1000000; // 1 second = 10^6 microseconds
pkt.pts = AV_NOPTS_VALUE; // is it okay?
pkt.dts = dts;
// and no need to set pkt.duration, right?

对于音频:(与视频完全一样,对吧?)

...................
...................

int64_t dts = av_gettime(); // get current time in microseconds
dts *= 9000; 
dts /= 1000000; // 1 second = 10^6 microseconds
pkt.pts = AV_NOPTS_VALUE; // is it okay?
pkt.dts = dts;
// and no need to set pkt.duration, right?

而且我认为他们现在就像共享相同的 currDts,对吧?如果我有任何错误或遗漏任何内容,请纠正我。

此外,如果我想将视频流时基用作 (AVRational){1, frameRate} 并将音频流时基用作 (AVRational){1, sampleRate},正确的代码应该是什么样子?

编辑 2.0:

m_pAudioStream->time_base = (AVRational){1, VIDEO_FRAME_RATE};
m_pVideoStream->time_base = (AVRational){1, VIDEO_FRAME_RATE};

bool AudioVideoRecorder::WriteAudio(const unsigned char *pEncodedData, size_t iDataSize) {
    ...........................
    ......................
    AVPacket pkt = {0};
    av_init_packet(&pkt);

    int64_t dts = av_gettime() / 1000; // convert into millisecond
    dts = dts * VIDEO_FRAME_RATE;
    if(m_dtsOffset < 0) {
        m_dtsOffset = dts;
    }

    pkt.pts = AV_NOPTS_VALUE;
    pkt.dts = (dts - m_dtsOffset);

    pkt.stream_index = m_pAudioStream->index;
    pkt.flags |= AV_PKT_FLAG_KEY;
    pkt.data = (uint8_t*) pEncodedData;
    pkt.size = iDataSize;

    int ret = av_interleaved_write_frame(m_pFormatCtx, &pkt);
    if(ret < 0) {
        LogErr("Writing audio frame failed: %d", ret);
        return false;
    }

    Log("Writing audio frame done.");

    av_free_packet(&pkt);
    return true;
}

bool AudioVideoRecorder::WriteVideo(const unsigned char *pData, size_t iDataSize, bool const bIFrame) {
    ........................................
    .................................
    AVPacket pkt = {0};
    av_init_packet(&pkt);
    int64_t dts = av_gettime() / 1000;
    dts = dts * VIDEO_FRAME_RATE;
    if(m_dtsOffset < 0) {
        m_dtsOffset = dts;
    }
    pkt.pts = AV_NOPTS_VALUE;
    pkt.dts = (dts - m_dtsOffset);

    if(bIFrame) {
        pkt.flags |= AV_PKT_FLAG_KEY;
    }
    pkt.stream_index = m_pVideoStream->index;
    pkt.data = (uint8_t*) pData;
    pkt.size = iDataSize;

    int ret = av_interleaved_write_frame(m_pFormatCtx, &pkt);

    if(ret < 0) {
        LogErr("Writing video frame failed.");
        return false;
    }

    Log("Writing video frame done.");

    av_free_packet(&pkt);
    return true;
}

最后的改动还好吗?视频和音频似乎是同步的。唯一的问题是 - 无论数据包是否延迟到达,音频都不会延迟播放。 喜欢 -

数据包到达:1 2 3 4 ...(然后下一帧在 3 秒后到达).. 5

播放的音频:1 2 3 4(无延迟)5

编辑 3.0:

归零音频样本数据:

AVFrame* pSilentData;
pSilentData = av_frame_alloc();
memset(&pSilentData->data[0], 0, iDataSize);

pkt.data = (uint8_t*) pSilentData;
pkt.size = iDataSize;

av_freep(&pSilentData->data[0]);
av_frame_free(&pSilentData);

这样可以吗?但是将其写入文件容器后,播放媒体时出现点点噪音。有什么问题吗?

编辑 4.0:

嗯,For µ-Law audio the zero value is represented as 0xff .所以——

memset(&pSilentData->data[0], 0xff, iDataSize);

解决我的问题。

最佳答案

时间戳(例如 dts )应该以 AVStream.time_base 为单位。您正在请求 1/90000 的视频时基和默认的音频时基 (1/9000),但您使用的是 1/100000 的时基来写入 dts 值。我也不确定是否能保证在写入 header 期间维护请求的时基,您的 muxer 可能会更改值并期望您处理新值。

所以代码如下:

int64_t dts = av_gettime();
dts = av_rescale_q(dts, (AVRational){1, 1000000}, (AVRational){1, 90000});
int duration = AUDIO_STREAM_DURATION; // 20
if(m_prevAudioDts > 0LL) {
    duration = dts - m_prevAudioDts;
}

不会工作。将其更改为使用音频流时基的内容,除非您知道自己在做什么,否则不要设置持续时间。 (视频相同。)

m_prevAudioDts = dts;
pkt.pts = AV_NOPTS_VALUE;
pkt.dts = m_currAudioDts;
m_currAudioDts += duration;
pkt.duration = duration;

这看起来令人毛骨悚然,尤其是与类似视频的代码结合使用时。这里的问题是,无论流之间的数据包间延迟如何,两者的第一个数据包的时间戳都将为零。您需要在所有流之间共享一个父 currDts,否则您的流将永远不同步。

[编辑]

因此,关于您的编辑,如果您有音频间隙,我认为您需要在间隙期间插入静音(归零音频样本数据)。

关于c++ - 正确计算PTS和DTS同步音视频ffmpeg C++,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31973107/

相关文章:

c++ - VS 2015 : The breakpoint will not currently be hit. 没有为此文档加载符号

c++ - 如何让指针的深层拷贝将内部结构发送到内核?

audio - 使用 FFmpeg 从音频接口(interface)录制时丢弃 channel

performance - 从程序访问远程视频

c++ - 为什么这样的循环在输入字符串时不会捕获错误?

C++ 类实例

javascript - 开启循环后如何让<audio>播放

python - pydub 可以知道文件是否曾经播放过吗?

http - 我需要多少种视频格式? - 网络

html - (HTML/CSS)从同一页面链接到嵌入式YouTube视频的特定时间