一个时间戳重建机制

一个时间戳重建机制
场景介绍

在实际的音视频时间戳处理中,有时候会遇到时间戳异常的问题,比如:时间戳跨度太大,或时间戳非单调递增。

因为对于时间戳的问题,不同的场景有不同的解决方法,所以,首先先介绍一下本文的场景。本文中所设计的时间戳重建机制主要是为了能够解决输入为连续不断的一些帧(音频或者视频),这些帧直接存在时间戳跨度太大,或时间戳非单调递增问题,期望经过时间戳重建,输出连续的、单调递增、间隔正确的序列。可以参考的实际场景类似多个视频文件拼接成一个视频文件。

解决方案

** 思路 **

其实这个场景源是多个不同的时间戳体系,目标是输出一个时间戳体系。对于这种“时间戳跨度太大,或时间戳非单调递增”的场景,我们需要对关注“时间戳间隔”这个值。在正常的媒体流中,时间戳间隔基本保持在一个很小的范围内,所以若超过正常范围,则认为时间戳发生异常。

例如:视频时间戳间隔在帧率为25帧每秒的情况下是1000ms/25=40ms,在30帧每秒的情况下是1000ms/30=33.333ms(不可整除),因为时间戳在实际代码操作中往往是整数,所以时间戳间隔在33ms上下来回抖动。

流程图

** 代码实现 **

下面的代码片段是我在实际使用中实现的时间戳重建机制。利用它可以将接收到的不稳定的时间戳重建成单调递增且同步。

#define SMEM_MAX_STREAM     64
#define SMEM_TIME_BASE      1000000
#define SMEM_TIME_BASE_Q    (AVRational){1, SMEM_TIME_BASE}

#define SMEM_NUM_IN_RANGE(n,min,max)  ((n) > (max) ? (max) : ((n) < (min) ? (min) : (n)))
#define SMEM_NUM_IF_OUT_RANGE(n,min,max)  ((n) > (max) ? 1 : ((n) < (min) ? 1 : 0))

typedef struct smem_dec_ctx {
    const AVClass *class;

    // for timestamp
    int       base_stream_start;
    int64_t   out_index; // the stream index of the out timestamp base used
    int64_t   last_out_ts[SMEM_MAX_STREAM]; // the last out ts for each stream,timebase={1, 1000000}
    int64_t   last_in_ts[SMEM_MAX_STREAM]; // the last in ts for each stream
    int64_t   first_in_ts[SMEM_MAX_STREAM];
    int should_duration[SMEM_MAX_STREAM];
    AVRational in_timebase[SMEM_MAX_STREAM];

}smem_dec_ctx;

static int rebuild_timestamp(AVFormatContext *avctx, struct memory_info * m_info, int64_t * out_pts, int64_t * out_dts)
{
    struct smem_dec_ctx * ctx = avctx->priv_data;

    av_log(avctx, AV_LOG_VERBOSE, "[rebuild_timestamp] before rebuild pts: %lld, dts: %lld\n", m_info->pts, m_info->dts);

    // 将时间戳转换为指定时间基础
    int64_t pts = av_rescale_q(m_info->pts, ctx->in_timebase[m_info->index], SMEM_TIME_BASE_Q);
    int64_t dts = av_rescale_q(m_info->dts, ctx->in_timebase[m_info->index], SMEM_TIME_BASE_Q);

    int64_t dt; // 记录差值

    if(m_info->index == 0){
        // the first number stream is the timestamp rebuild base stream 

        if(SMEM_NUM_IF_OUT_RANGE(dts - ctx->last_in_ts[m_info->index], 0, 5*ctx->should_duration[m_info->index])){
            ctx->first_in_ts[m_info->index] = dts;

            av_log(avctx, AV_LOG_WARNING, "[rebuild_timestamp] index:%d, last dts: %lld, the new dts: %lld is out of the range\n", m_info->index, ctx->last_in_ts[m_info->index], dts);
            *out_dts = ctx->last_out_ts[m_info->index] + ctx->should_duration[m_info->index];
            

        }else{
            *out_dts = ctx->last_out_ts[m_info->index] + (dts - ctx->last_in_ts[m_info->index]);
        }

        *out_pts = *out_dts + (pts - dts) + 2*ctx->should_duration[m_info->index];

        ctx->base_stream_start = 1;

    }else{
        if(ctx->base_stream_start != 1){
            av_log(avctx, AV_LOG_WARNING, "[rebuild_timestamp] index:%d, base stream not start, skip.\n", m_info->index);
            return -1;
        }

        // other number streams check the timestamp by the base stream
        if(SMEM_NUM_IF_OUT_RANGE(dts - ctx->last_in_ts[m_info->index], 0, 5*ctx->should_duration[m_info->index])){
            ctx->first_in_ts[m_info->index] = dts;

            av_log(avctx, AV_LOG_WARNING, "[rebuild_timestamp] index:%d, last dts: %lld, the new dts: %lld is out of the range\n", m_info->index, ctx->last_in_ts[m_info->index], dts);
            

            // the others streams should after base stream
            if(ctx->first_in_ts[m_info->index] < ctx->first_in_ts[0]){
                av_log(avctx, AV_LOG_WARNING, "[rebuild_timestamp] index:%d, the local stream time(%lld) < base stream time(%lld), skip.\n"
                    , m_info->index, ctx->first_in_ts[m_info->index], ctx->first_in_ts[0]);

                return -1;
            }

            dt = ctx->first_in_ts[m_info->index] - ctx->first_in_ts[0]; // the dt of the new streams

            // the streams out ts should same as dt
            *out_dts = ctx->last_out_ts[0] + SMEM_NUM_IN_RANGE(dt, ctx->should_duration[m_info->index], 10*ctx->should_duration[m_info->index]);

            if(*out_dts < ctx->last_out_ts[m_info->index]){
                av_log(avctx, AV_LOG_WARNING, "[rebuild_timestamp] index:%d, the local out time(%lld) < last out time(%lld), skip.\n",
                    m_info->index,*out_dts, ctx->last_out_ts[m_info->index]);
                return -1;
            }

        }else{


            *out_dts = ctx->last_out_ts[m_info->index] + (dts - ctx->last_in_ts[m_info->index]);
        }

        *out_pts = *out_dts + (pts - dts) + 2*ctx->should_duration[m_info->index];

    }

    ctx->last_in_ts[m_info->index] = dts;
    ctx->last_out_ts[m_info->index] = *out_dts;


    av_log(avctx, AV_LOG_VERBOSE, "[rebuild_timestamp] after rebuild pts: %lld, dts: %lld\n", *out_pts, *out_dts);


    return 0;
}

转载于:https://my.oschina.net/zhangxu0512/blog/732282

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值