当前位置: 首页 > 知识库问答 >
问题:

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

乐宜民
2023-03-14

我正在尝试mux 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;
}

编辑:

根据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?

另外,如果我想使用视频流时基作为(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;
}
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:

对于μ-law音频,零值表示为0xff。所以-

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

解决我的问题。

共有1个答案

汪正雅
2023-03-14

时间戳(例如dts)应该以avstream.time_base为单位。您请求的是1/90000的视频时基和默认的音频时基(1/9000),但您使用的是1/100000的时基来写入dts值。我也不确定是否保证在头写入期间维护所请求的时间基准,您的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,否则您的流将永远不同步。

[编辑]

所以,关于你的编辑,如果你有音频间隙,我认为你需要插入静音(归零音频样本数据)的间隙持续时间。

 类似资料:
  • 我正在从两个不同的线程接收视频H264编码数据和音频G.711 PCM编码数据,以mux/写入多媒体容器。 writer函数签名如下所示: 提前感谢! 编辑:在我的视频流中,没有B帧。所以,我认为这里PTS和DTS可以保持不变。

  • 当从avi转码到mp4(x264)时,我遇到了fps问题。最终问题出在PTS和DTS值上,所以在AV_INTLEAVED_WRITE_FRAME函数之前添加的第12-15行: 看了很多帖子我还是不明白: =1/25和=1/12800。第一个是我设置的,但我不知道为什么和谁设置了12800?我注意到,在第(7)行=1/90000之前,紧接着它变成了1/12800,为什么?当我从avi转码到avi时,

  • 使用ffmpeg concat连接多个文件似乎会导致音频的时间戳或偏移不匹配。我试过几个视频,发现H.264/MP4也有同样的问题。 使用并对视频进行编码似乎可以很好地工作。当ffmpeg执行全部转换计算时,音频保持同步,似乎可以得到所有正确的结果。 然而,简单地将视频级联而不进行任何转换或编码会导致同步问题缓慢增加。显然,对视频进行编码而不是简单地加入它们将导致信息/质量的损失,所以我宁愿找到一

  • 非单调递增的dts是什么意思? 如何使用libavformat以相同的编解码器连接2个视频文件? 流中dts到muxer的非单调递增 我相信当我增加pts和DTS时,我有正确的逻辑。我不知道我到底错过了什么。 谢了。

  • 我有一个。mp4视频文件,包含高级3.1“avc1.64001f”视频和低复杂度AAC“mp4a.40.2”音频。 如何通过使用libfdk_aacFFmpeg编码器将低复杂度AAC转换为“HE-AAC”音频编解码器并保持/获得尽可能好的质量?我只想将“avc1.64001f”视频复制到目标文件中。