Как установить pts и dts AVPacket из временных меток RTP при мультиплексировании потока RTP VP8 в webm с помощью ffmpeg libavformat?

Я использую библиотеку ffmpeg libavformat для записи файла webm только для видео. Я получаю поток rtp в кодировке VP8 на свой сервер. Я успешно сгруппировал поток байтов rtp (из полезной нагрузки rtp) в отдельные кадры и создал AVPacket. Я НЕ перекодирую полезную нагрузку в VP8, поскольку она уже закодирована в vp8.

Я записываю AVPacket в файл с помощью метода av_write_interleaved (). Хотя на выходе я получаю файл webm, он вообще не воспроизводится. Когда я проверил информацию о файле с помощью команды mkvfo инструмента mkv, я обнаружил следующую информацию:

+ EBML head
|+ EBML version: 1
|+ EBML read version: 1
|+ EBML maximum ID length: 4
|+ EBML maximum size length: 8
|+ Doc type: webm
|+ Doc type version: 2
|+ Doc type read version: 2
+ Segment, size 2142500
|+ Seek head (subentries will be skipped)
|+ EbmlVoid (size: 170)
|+ Segment information
| + Timestamp scale: 1000000
| + Multiplexing application: Lavf58.0.100
| + Writing application: Lavf58.0.100
| + Duration: 78918744.480s (21921:52:24.480)
|+ Segment tracks
| + A track
|  + Track number: 1 (track ID for mkvmerge & mkvextract: 0)
|  + Track UID: 1
|  + Lacing flag: 0
|  + Name: Video Track
|  + Language: eng
|  + Codec ID: V_VP8
|  + Track type: video
|  + Default duration: 1.000ms (1000.000 frames/fields per second for a 
video track)
|  + Video track
|   + Pixel width: 640
|   + Pixel height: 480
|+ Tags
| + Tag
|  + Targets
|  + Simple
|   + Name: ENCODER
|   + String: Lavf58.0.100
| + Tag
|  + Targets
|   + TrackUID: 1
|  + Simple
|   + Name: DURATION
|   + String: 21921:52:24.4800000
|+ Cluster

Как видим, продолжительность стрима непропорционально велика. (Моя допустимая продолжительность трансляции должна быть около 8-10 секунд). И частота кадров в информации о дорожке также не такая, какой я ее задаю. Я устанавливаю частоту кадров 25 кадров в секунду.

Я применяю av_scale_q (rtpTimeStamp, codec_timebase, stream_timebase) и устанавливаю масштабированный rtpTimeStamp как значения pts и dts. Я предполагаю, что мой способ установки pts и dts неверен. Пожалуйста, помогите мне, как установить значения pts и dts в AVPacket, чтобы получить рабочий файл webm с правильной метаинформацией на нем.

РЕДАКТИРОВАТЬ:

Ниже приведен код, который я вызываю для инициализации библиотеки:

 #define STREAM_FRAME_RATE 25
 #define STREAM_PIX_FMT AV_PIX_FMT_YUV420P 

 typedef struct OutputStream {
   AVStream *st;
   AVCodecContext *enc;
   AVFrame *frame;
 } OutputStream;


 typedef struct WebMWriter {
      OutputStream *audioStream, *videoStream;
      AVFormatContext *ctx;
      AVOutputFormat *outfmt;
      AVCodec *audioCodec, *videoCodec;
 } WebMWriter;

 static OutputStream audioStream = { 0 }, videoStream = { 0 };

 WebMWriter *init(char *filename)
 {
    av_register_all();

    AVFormatContext *ctx = NULL;
    AVCodec *audioCodec = NULL, *videoCodec = NULL;
    const char *fmt_name = NULL;
    const char *file_name = filename;

    int alloc_status = avformat_alloc_output_context2(&ctx, NULL, fmt_name, file_name);

    if(!ctx)
            return NULL;

    AVOutputFormat *fmt = (*ctx).oformat;

    AVDictionary *video_opt = NULL;
    av_dict_set(&video_opt, "language", "eng", 0);
    av_dict_set(&video_opt, "title", "Video Track", 0);

    if(fmt->video_codec != AV_CODEC_ID_NONE)
    {
            addStream(&videoStream, ctx, &videoCodec, AV_CODEC_ID_VP8, video_opt);
    }

 if(videoStream.st)
            openVideo1(&videoStream, videoCodec, NULL);

    av_dump_format(ctx, 0, file_name, 1);

    int ret = -1;
    /* open the output file, if needed */
    if (!(fmt->flags & AVFMT_NOFILE)) {
            ret = avio_open(&ctx->pb, file_name, AVIO_FLAG_WRITE);
            if (ret < 0) {
                    printf("Could not open '%s': %s\n", file_name, av_err2str(ret));
                    return NULL;
            }
    }

    /* Write the stream header, if any. */
    AVDictionary *format_opt = NULL;
    ret = avformat_write_header(ctx, &format_opt);
    if (ret < 0) {
            fprintf(stderr, "Error occurred when opening output file: %s\n",
                            av_err2str(ret));
            return NULL;
    }


    WebMWriter *webmWriter = malloc(sizeof(struct WebMWriter));
    webmWriter->ctx = ctx;
    webmWriter->outfmt = fmt;
    webmWriter->audioStream = &audioStream;
    webmWriter->videoStream = &videoStream;
    webmWriter->videoCodec = videoCodec;

    return webmWriter;
 }

Ниже приводится метод openVideo ():

 void openVideo1(OutputStream *out_st, AVCodec *codec, AVDictionary *opt_arg)
 {       
    AVCodecContext *codec_ctx = out_st->enc;
    int ret = -1;
    AVDictionary *opt = NULL;
    if(opt_arg != NULL)
    {       
            av_dict_copy(&opt, opt_arg, 0);
            ret = avcodec_open2(codec_ctx, codec, &opt);
    }
    else
    {       
            ret = avcodec_open2(codec_ctx, codec, NULL);
    }

    /* copy the stream parameters to the muxer */
    ret = avcodec_parameters_from_context(out_st->st->codecpar, codec_ctx);
    if (ret < 0) {
            printf("Could not copy the stream parameters\n");
            exit(1);
    }

 }

Ниже приводится метод addStream ():

 void addStream(OutputStream *out_st, AVFormatContext *ctx, AVCodec **cdc, enum AVCodecID codecId, AVDictionary *opt_arg)
 {

    (*cdc) = avcodec_find_encoder(codecId);
    if(!(*cdc)) {
            exit(1);
    }

    /*as we are passing a NULL AVCodec cdc, So AVCodecContext codec_ctx will not be allocated, we have to do it explicitly */
    AVStream *st = avformat_new_stream(ctx, *cdc);
    if(!st) {
            exit(1);
    }

    out_st->st = st;
    st->id = ctx->nb_streams-1;

    AVDictionary *opt = NULL;
    av_dict_copy(&opt, opt_arg, 0);
    st->metadata = opt;

    AVCodecContext *codec_ctx = st->codec;
    if (!codec_ctx) {
            fprintf(stderr, "Could not alloc an encoding context\n");
            exit(1);
    }
    out_st->enc = codec_ctx;

    codec_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;

 switch ((*cdc)->type) {
            case AVMEDIA_TYPE_AUDIO:
                    codec_ctx->codec_id = codecId;
                    codec_ctx->sample_fmt  = AV_SAMPLE_FMT_FLTP;
                    codec_ctx->bit_rate    = 64000;
                    codec_ctx->sample_rate = 48000;
                    codec_ctx->channels    = 2;//1;
                    codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; 
                    codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;
                    codec_ctx->time_base = (AVRational){1,STREAM_FRAME_RATE};


                    break;

            case AVMEDIA_TYPE_VIDEO:
                    codec_ctx->codec_id = codecId;
                    codec_ctx->bit_rate = 90000;
                    codec_ctx->width    = 640;
                    codec_ctx->height   = 480;


                    codec_ctx->time_base = (AVRational){1,STREAM_FRAME_RATE};
                    codec_ctx->gop_size = 12;
                    codec_ctx->pix_fmt = STREAM_PIX_FMT;
                    codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;

                    break;

            default:
                    break;
    }

 /* Some formats want stream headers to be separate. */
    if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
            codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
 }

Ниже приведен код, который я вызываю для записи фрейма данных в файл:

 int writeVideoStream(AVFormatContext *ctx, AVStream *st, uint8_t *data, int size, long frameTimeStamp, int isKeyFrame, AVCodecContext *codec_ctx)
 {       
    AVRational rat = st->time_base;
    AVPacket pkt = {0};
    av_init_packet(&pkt);

    void *opaque = NULL;
    int flags = AV_BUFFER_FLAG_READONLY;
    AVBufferRef *bufferRef = av_buffer_create(data, size, NULL, opaque, flags);

    pkt.buf = bufferRef;
    pkt.data = data;
    pkt.size = size;  
    pkt.stream_index  = st->index;

    pkt.pts = pkt.dts = frameTimeStamp;
    pkt.pts = av_rescale_q(pkt.pts, codec_ctx->time_base, st->time_base);
    pkt.dts = av_rescale_q(pkt.dts, codec_ctx->time_base, st->time_base);


    if(isKeyFrame == 1)
            pkt.flags |= AV_PKT_FLAG_KEY;

    int ret = av_interleaved_write_frame(ctx, &pkt);
    return ret;
 }

ПРИМЕЧАНИЕ. Здесь frameTimeStamp - это отметка времени rtp в пакете rtp этого кадра.

ИЗМЕНИТЬ 2.0:

Мой обновленный метод addStream () с изменениями кодеков:

 void addStream(OutputStream *out_st, AVFormatContext *ctx, AVCodec **cdc, enum AVCodecID codecId, AVDictionary *opt_arg)
 {

    (*cdc) = avcodec_find_encoder(codecId);
    if(!(*cdc)) {
            printf("@@@@@ couldnt find codec \n");
            exit(1);
    }

    AVStream *st = avformat_new_stream(ctx, *cdc);
    if(!st) {
            printf("@@@@@ couldnt init stream\n");
            exit(1);
    }

    out_st->st = st;
    st->id = ctx->nb_streams-1;
    AVCodecParameters *codecpars = st->codecpar;
    codecpars->codec_id = codecId;
    codecpars->codec_type = (*cdc)->type;

    AVDictionary *opt = NULL;
    av_dict_copy(&opt, opt_arg, 0);
    st->metadata = opt;
    //av_dict_free(&opt);

    AVCodecContext *codec_ctx = st->codec;
    if (!codec_ctx) {
            fprintf(stderr, "Could not alloc an encoding context\n");
            exit(1);
    }
    out_st->enc = codec_ctx;

    //since opus is experimental codec
    //codec_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;

 switch ((*cdc)->type) {
            case AVMEDIA_TYPE_AUDIO:
                    codec_ctx->codec_id = codecId;
                    codec_ctx->sample_fmt  = AV_SAMPLE_FMT_FLTP;//AV_SAMPLE_FMT_U8 or AV_SAMPLE_FMT_S16;
                    codec_ctx->bit_rate    = 64000;
                    codec_ctx->sample_rate = 48000;
                    codec_ctx->channels    = 2;//1;
                    codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; //AV_CH_LAYOUT_MONO;
                    codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;
                    codec_ctx->time_base = (AVRational){1,STREAM_FRAME_RATE};

                    codecpars->format = codec_ctx->sample_fmt;
                    codecpars->channels = codec_ctx->channels;
                    codecpars->sample_rate = codec_ctx->sample_rate;

                    break;

            case AVMEDIA_TYPE_VIDEO:
                    codec_ctx->codec_id = codecId;
                    codec_ctx->bit_rate = 90000;
                    codec_ctx->width    = 640;
                    codec_ctx->height   = 480;

                    codec_ctx->time_base = (AVRational){1,STREAM_FRAME_RATE};
                    codec_ctx->gop_size = 12;
                    codec_ctx->pix_fmt = STREAM_PIX_FMT;
                    //codec_ctx->max_b_frames = 1;
                    codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
                    codec_ctx->framerate = av_inv_q(codec_ctx->time_base);
                    st->avg_frame_rate = codec_ctx->framerate;//(AVRational){25000, 1000};

                    codecpars->format = codec_ctx->pix_fmt;
                    codecpars->width = codec_ctx->width;
                    codecpars->height = codec_ctx->height;
                    codecpars->sample_aspect_ratio = (AVRational){codec_ctx->width, codec_ctx->height};

                    break;

            default:
                    break;
    }      
    codecpars->bit_rate = codec_ctx->bit_rate;

    int ret = avcodec_parameters_to_context(codec_ctx, codecpars);
    if (ret < 0) {
            printf("Could not copy the stream parameters\n");
            exit(1);
    }

    /* Some formats want stream headers to be separate. */
    if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
            codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
 }

person user2595786    schedule 25.01.2018    source источник
comment
Пожалуйста, добавьте свой код, в котором вы вычисляете и устанавливаете значения pts, перед callig av_interleaved_write_frame. Так мы сможем лучше увидеть, что не так.   -  person the kamilz    schedule 25.01.2018
comment
Извините за задержку. Я добавил свой код в сообщение.   -  person user2595786    schedule 29.01.2018


Ответы (1)


Я думаю, что вы правы в вычислении pts / dts, это проблема, используйте эту формулу для ручного расчета временных меток, посмотрите, работает ли она, тогда вы можете сделать это с помощью av_rescale_q.

Вот моя протестированная формула (для сырого (yuv) вывода):

int64_t frameTime;
int64_t frameDuration;

frameDuration = video_st->time_base.den / video_fps; // i.e. 25
frameTime     = frame_count * frameDuration;
pkt->pts      = frameTime / video_st->time_base.num;
pkt->duration = frameDuration;

pkt->dts          = pkt->pts;
pkt->stream_index = video_st->index;

Используйте это перед av_interleaved_write_frame.
Примечание: frame_count это счетчик, который увеличивается после каждого вывода видеокадра (с av_interleaved_write_frame).

person the kamilz    schedule 29.01.2018
comment
Спасибо за быстрый ответ. Я использовал вашу формулу. Это изменило продолжительность в информации о сегменте, и продолжительность отображается правильно. Но я не вижу видеокадров при воспроизведении файла webm в vlc. Я имею ввиду, что видео прогрессирует, но на видео не видно ни одного кадра. Кроме того, продолжительность по умолчанию в «Информация о дорожке» по-прежнему неверна. Это все еще 1000 кадров в секунду, которые я никогда не устанавливал. - person user2595786; 29.01.2018
comment
И, когда я использовал av_rescale_q (), продолжительность pkt больше, чем должна быть. Например, если фактическая продолжительность составляет 10 секунд, продолжительность, установленная с помощью av_rescale_q () of pts, составляет около 6 минут 30 секунд. - person user2595786; 29.01.2018
comment
установить частоту кадров потока вручную с помощью video_st->avg_frame_rate.num = 25000; и video_st->avg_frame_rate.den = 1000. Ставьте эти свои addStream() TYPE_VIDEO case. Обратите внимание, что это для видео со скоростью 25 кадров в секунду, измените соответственно, если у вас другие. И лучше использовать AVCodecParameters для установки параметров кодека, таких как ширина и высота кадра. - person the kamilz; 29.01.2018
comment
Ну и распечатайте параметры video_st->time_base. Den и num, чтобы убедиться, что они верны. - person the kamilz; 29.01.2018
comment
Я добавил avg_frame_rate в video_st, как вы упомянули. После этого «Длительность по умолчанию» в «Информация о дорожке» действительно изменилась на правильное значение в соответствии с 25 кадрами в секунду. Но когда я распечатал значения Den и num для video_st- ›time_base, эти значения были num = 1, Den = 1000 (это означает, что для расчетов все еще учитывается 1000 кадров в секунду). Я не знаю, почему это происходит. - person user2595786; 29.01.2018
comment
И все же я не вижу ни одного кадра видео в vlc, когда я его проигрываю. - person user2595786; 29.01.2018
comment
попробуйте заменить эти строки codec_ctx->time_base = (AVRational){1,STREAM_FRAME_RATE}; на это codec_ctx->time_base = (AVRational){STREAM_FRAME_RATE, 1}; - person the kamilz; 29.01.2018
comment
Я сделал изменение codec_ctx- ›time_base. По-прежнему то же значение video_st-time_base num = 1, den = 1000. Но зачем это изменение? Мой способ установки time_base кодека кажется правильным. И почему я не вижу видео во время его воспроизведения? Кстати, у меня не сырое видео. У меня уже есть поток rtp в кодировке VP8. - person user2595786; 29.01.2018
comment
возможно, вам следует использовать AVCodecParameters для установки параметров кодека. - person the kamilz; 29.01.2018
comment
И вы проверили это: stackoverflow.com/questions/46571544/ - person the kamilz; 29.01.2018
comment
Я только что проверил. Я не вижу видео, когда воспроизводю его в плеере, хотя значения кадров в секунду и длительности верны. - person user2595786; 29.01.2018
comment
**** возможно, вам следует использовать AVCodecParameters для установки параметров кодека. *****. Под этим вы имеете в виду установку параметров контекста кодека с использованием метода avcodec_parameters_to_context () из utils.c libavcodec? - person user2595786; 29.01.2018
comment
вроде, замените avcodec_parameters_from_context на AVCodecParameters *par = out_st->st->codecpar; и avcodec_parameters_to_context(codec_ctx, out_st->st->codecpar) - person the kamilz; 29.01.2018
comment
Я внес изменения, о которых вы сказали, установив кодек-пар с помощью AVCodecParameters и используя метод avcodec_parameters_to_context (). Я обновил свой новый метод addStream () в сообщении. Но я все еще не могу посмотреть видео в плеере. Вся метаинформация в файле webm верна, но видео не видно. - person user2595786; 30.01.2018
comment
Можете ли вы загрузить где-нибудь образец видео (может хватить нескольких минут), чтобы я мог его загрузить и проанализировать? - person the kamilz; 30.01.2018
comment
Я загрузил образец видео на свой Google Диск. Вот ссылка: drive.google.com/open?id=17XTgpb6PM27qiHoGKa - person user2595786; 30.01.2018
comment
Да, метаданные кажутся правильными, но что-то не так, особенно в hexdump, компилируется ли ваш проект с помощью gcc + makefile, если да и если хотите, вы можете поместить проект куда-нибудь и позволить мне протестировать его на моем сервере ubuntu. Это может ускорить процесс фиксации. - person the kamilz; 30.01.2018
comment
Конечно. Но потребуется немного времени, чтобы внести некоторые изменения. Я сообщу вам, как только закончу с этими изменениями. Вывести обсуждение на конфиденциальный режим? могу я получить ваш почтовый идентификатор или что-то еще, чтобы мы могли продолжить там? - person user2595786; 30.01.2018
comment
Конечно, по скайпу: [email protected]. Я на работе, поэтому не могу ответить быстро. - person the kamilz; 30.01.2018
comment
Братан, я не могу отправить тебе письмо по указанному выше идентификатору. Пожалуйста, дайте мне свой почтовый идентификатор, чтобы я мог отправить письмо. - person user2595786; 30.01.2018
comment
Я ожидал общаться через Skype, в любом случае мой адрес электронной почты: [email protected] - person the kamilz; 31.01.2018