解码后的 AVFrame 的 pts 点位于样本末尾?

问题描述 投票:0回答:1

我正在使用 FFMPEG 解码视频,解码后的音频帧具有奇怪的 pts 值。

这是输出:

Audio Input 13 13 576 
Audio Input 36 23 1024
Audio Input 59 23 1024
Audio Input 82 23 1024 
Audio Input 105 23 1024
Audio Input 129 24 1024

第一个数字是

AVFrame->pts
,第二个数字是
AVFrame->pts - previous AVFrame->pts
,第三个数字是
AVFrame->nb_samples

解码器的time_base为1/1000,采样率为44100。

似乎 pts 指向样本的末尾,而不是开头。

第一个

AVFrame
包含516个样本,
516/44100*1000=13.06...
等于第一个
AVFrame->pts

第二个

AVFrame
包含 1024 个样本,
1024/44100*1000=23.21...
等于第二个
AVFrame->pts
- 第一个
AVFrame->pts

这正常吗?

因为我会将这些

AVFrame
放入
amix
过滤器中。
amix
过滤器将以 1/44100 时基重新缩放点,这等于
13 / 1000 * 44100 = 573
,但我只要求 64 个样本,因此
amix
首先返回
AVFrame
,其中包含 573 个点和 64 个 nb_samples。这绝对是错误的。如果我继续要求更多样本,
amix
将会随着分数的增加而返回。像这样:

Audio Frame 573 573 64
Audio Frame 637 64 64
Audio Frame 701 64 64
Audio Frame 765 64 64
Audio Frame 829 64 64
Audio Frame 893 64 64
Audio Frame 957 64 64
Audio Frame 1021 64 64
Audio Frame 1085 64 64

更糟糕的是,如果解码器返回较小的 nb_samples。

amix
将返回比上一个少的分数。

Audio Input 8488 23 1024
Audio Frame 374321 54 64
Audio Frame 374385 64 64
Audio Frame 374449 64 64
Audio Frame 374513 64 64
Audio Frame 374577 64 64
Audio Frame 374641 64 64
Audio Frame 374705 64 64
Audio Frame 374769 64 64
Audio Frame 374833 64 64
Audio Frame 374897 64 64
Audio Frame 374961 64 64
Audio Frame 375025 64 64
Audio Frame 375089 64 64
Audio Frame 375153 64 64
Audio Frame 375217 64 64
Audio Frame 375281 64 64
Audio Input 8501 13 576
Audio Frame 374894 -387 64

8488 / 1000 * 44100 = 374320.8

374321 + 64 * (1024/64-1) = 375281

但是

8501/1000*44100=374894 < 375281
,dts正在减少。

我可以通过移动解码的

AVFrame
的分数来解决这个问题,但我不确定我做得对吗。

我使用的ffmpeg版本是

n5.1.2-f31542651f.clean
(嗯,vcpkg将源代码存储在这个目录中,我猜就是这个版本?)

音频解码器是

libvorbis
,容器格式是 webm,视频解码器是
libvpx v1.12.0

这是视频解码代码:

#include "Decoder.h"
#include <exception>
#include <stdexcept>
#include <iostream>


Decoder::Decoder(const char *filename)
    : formatContext(nullptr, closeAVFormatContext),
      videoCodecContext(nullptr, closeAVCodecContext),
      audioCodecContext(nullptr, closeAVCodecContext),
      avVideoFrame(newAVFramePtr()),
      avPacket(newAVPacketPtr()),
      avAudioFrame(newAVFramePtr()) {

    int ret;
    {
        auto pContext = avformat_alloc_context();
        if(!pContext) {
            throw std::bad_alloc();
        }
        ret = avformat_open_input(&pContext, filename, nullptr, nullptr);
        throwAVError(ret, "avformat_open_input Error");
        this->formatContext.reset(pContext);
    }
    avformat_find_stream_info(this->formatContext.get(), nullptr);
    this->videoIndex = -1;
    this->audioIndex = -1;
    for (int i = 0; i < this->formatContext->nb_streams; ++i) {
        AVStream *pStream = this->formatContext->streams[i];
        if(pStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && this->videoIndex == -1) {
            this->_timeBase = pStream->time_base;
            this->videoIndex = i;
            this->videoParameters = pStream->codecpar;
            this->videoCodec = avcodec_find_decoder(this->videoParameters->codec_id);
            if(this->videoCodec == nullptr) {
                throw AVException(std::string("cannot found a suitable decoder ") + avcodec_get_name(this->videoParameters->codec_id));
            }
        }
        if(pStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && this->audioIndex == -1) {
            this->audioIndex = i;
            this->audioParameters = pStream->codecpar;
            this->sampleRate = this->audioParameters->sample_rate;
            this->audioCodec = avcodec_find_decoder(this->audioParameters->codec_id);
            if(this->audioCodec == nullptr) {
                throw AVException(std::string("cannot found a suitable decoder ") + avcodec_get_name(pStream->codecpar->codec_id));
            }
        }
        if(this->audioIndex != -1 && this->videoIndex != -1) {
            break;
        }
    }

    this->videoCodecContext = createCodecContext(this->videoParameters, this->videoCodec);
    this->audioCodecContext = createCodecContext(this->audioParameters, this->audioCodec);

}

AVCodecContextPtr Decoder::createCodecContext(AVCodecParameters *parameters, const AVCodec* codec) {
    AVCodecContextPtr context(nullptr, closeAVCodecContext);
    {
        auto ptr = avcodec_alloc_context3(codec);
        if(!ptr) {
            throw std::bad_alloc();
        }
        context.reset(ptr);
    }
    int ret = avcodec_parameters_to_context(context.get(), parameters);
    throwAVError(ret, "avcodec_parameters_to_context Error");
    ret = avcodec_open2(context.get(), codec, nullptr);
    throwAVError(ret, "avcodec_open2");
    return context;
}

Result Decoder::nextFrame() {
    int ret;
    do {
        ret = av_read_frame(this->formatContext.get(), this->avPacket.get());
        if(ret == AVERROR_EOF) {
            av_packet_unref(this->avPacket.get());
            return Result::eof;
        }
        throwAVError(ret, "av_read_frame Error");
    } while(this->avPacket->stream_index != this->videoIndex && this->avPacket->stream_index != this->audioIndex);
    bool isVideo = this->avPacket->stream_index == this->videoIndex;
    const auto& context = isVideo ? this->videoCodecContext : this->audioCodecContext;
    const auto& frame = isVideo ? this->avVideoFrame : this->avAudioFrame;
    ret = avcodec_send_packet(context.get(), this->avPacket.get());
    av_packet_unref(this->avPacket.get());
    throwAVError(ret, "avcodec_send_packet Error");
    ret = avcodec_receive_frame(context.get(), frame.get());
    if(ret == AVERROR(EAGAIN)) {
        return Result::again;
    }
    throwAVError(ret, "avcodec_receive_frame Error");
    return isVideo ? Result::video : Result::audio;
}



视频文件在这里下载:https://www.webmfiles.org/demo-files/

直接链接:https://dl6.webmfiles.org/big-buck-bunny_trailer.webm

ffmpeg multimedia
1个回答
0
投票

哈,vorbis-in-webm。请问这个文件是怎么生成的呢?我猜你有一个由某个 VP9 编码器(实际上哪个编码器并不重要)编码为临时文件格式(可能是 ivf)的视频文件,并且你将音频单独编码为 ogg/vorbis,并使用一些软件进行复用这两个一起进入webm。我想知道最后一步是用什么软件完成的。

我猜音频编码是分开的,音频的中间容器是 ogg,因为 ogg/vorbis 实际上将时间戳(“颗粒”)存储为数据包末尾而不是数据包开头。我相信您的复用软件不知道这一点,并在转换为 webm 时将颗粒(数据包结束)存储为时间戳(数据包开始)。显而易见的解决方案是在音频/视频混合阶段使用不同的软件,或者完全删除单独的步骤,只使用一个大的 ffmpeg 命令行进行编码。

(也可能有其他解释或错误,但请详细说明文件是如何生成的。)

© www.soinside.com 2019 - 2024. All rights reserved.