javacv ffmpeg获取rtsp绝对时间戳

javacv ffmpeg拉取rtsp流怎么获取到绝对时间戳,目前我使用javacv拉流dts和pts都是从0开始计数和rame.timestamp 也是从0开始,想问问如何获取到真实的时间戳。

要获取真实的时间戳,您需要使用视频帧的时间基准和时间戳。
时间基准是帧的时间单位,通常是“时钟频率”,也就是每秒钟的帧数。时间基准可以通过FFmpeg的AVStream结构体来获取。
针对每个AVPacket,可以通过解码器解码出一组AVFrame。每个AVFrame都有自己的时间戳,可以使用av_frame_get_best_effort_timestamp()函数获取。该函数会使用不同的时间戳来源来计算最佳时间戳,通常会优先使用PTS。
示例代码片段,可以获取视频帧的真实时间戳:

AVStream videoStream = container.getStream(videoStreamIndex);
int timeBaseNum = videoStream.getTimeBase().num();
int timeBaseDen = videoStream.getTimeBase().den();

// 解码一帧视频
AVPacket packet = new AVPacket();
av_read_frame(container, packet);
AVFrame frame = avcodec_alloc_frame();
int gotFrame = 0;
avcodec_decode_video2(decoder, frame, gotFrame, packet);

// 获取最佳时间戳
long bestEffortTimestamp = av_frame_get_best_effort_timestamp(frame);
if (bestEffortTimestamp != AV_NOPTS_VALUE) {
    // 计算真实时间戳
    long realTimestamp = (long) (bestEffortTimestamp * 1000.0 * timeBaseNum / timeBaseDen);
}

在这个示例中,我们首先获取了视频流的时间基准。然后,解码一个视频帧,并使用av_frame_get_best_effort_timestamp()函数获取最佳时间戳。最后,我们使用视频流的时间基准,以毫秒为单位计算出真实时间戳。

基于最新版ChatGPT4的回答,望采纳!!!有其他问题也可以询问我哦💕(最新版更智能,功能更加强大):
您可以通过使用FFmpeg的AVStream.time_base属性和AVFrame.best_effort_timestamp属性来获取RTSP流的时间戳。以下是一个示例代码,演示如何获取绝对时间戳:

java

import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avutil.AVFrame;
import org.bytedeco.javacv.*;
import java.util.concurrent.TimeUnit;

public class RTSPCapture {
    public static void main(String[] args) throws FrameGrabber.Exception, StreamException, InterruptedException {
        String rtspUrl = "rtsp://example.com/stream"; // 您的 RTSP URL
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(rtspUrl);
        grabber.start();

        // 获取流的time_base属性
        double timeBase = av_q2d(grabber.getFormatContext().streams(0).time_base());

        while (true) {
            Frame frame = grabber.grab();
            if (frame == null) {
                break;
            }

            if (frame instanceof VideoFrame) {
                AVFrame avFrame = ((VideoFrame) frame).avFrame();

                // 获取帧的best_effort_timestamp属性
                long timestamp = avFrame.best_effort_timestamp();

                // 将相对时间戳转换为绝对时间戳
                long absoluteTimestamp = (long) (timestamp * timeBase * 1000);

                // 将绝对时间戳转换为可读格式
                String formattedTime = String.format("%02d:%02d:%02d.%03d",
                        TimeUnit.MILLISECONDS.toHours(absoluteTimestamp),
                        TimeUnit.MILLISECONDS.toMinutes(absoluteTimestamp) % TimeUnit.HOURS.toMinutes(1),
                        TimeUnit.MILLISECONDS.toSeconds(absoluteTimestamp) % TimeUnit.MINUTES.toSeconds(1),
                        absoluteTimestamp % 1000);

                System.out.println("Absolute timestamp: " + formattedTime);
            }
        }

        grabber.stop();
    }

    // 辅助方法,将AVRational转换为double
    private static double av_q2d(org.bytedeco.ffmpeg.avutil.AVRational a) {
        return a.num() / (double) a.den();
    }
}

在上述代码中,我们首先获取流的time_base属性,并将其用作相对时间戳(即best_effort_timestamp)的比例因子,然后将其转换为绝对时间戳。最后,我们使用String.format()方法将绝对时间戳格式化为可读的时间格式。


所有画面的真实时间都遵循如下公式:

Time = PTS * 1/时间基

因此一个视频的总时长为:

Time = Duration_PTS * 1/时间基

我觉得要获取RTSP流的绝对时间戳,可以使用以下步骤:
1、获取取RTSP流中的帧的PTS和DTS,这可以通过使用FFmpeg的AVFrame结构体的pts和pkt_dts字段来完成。
2、获取RTSP流的开始时间戳,这可以通过使用FFmpeg的AVFormatContext结构体的start_time字段来完成。
3、计算帧的真实时间戳,将帧的PTS或DTS加上RTSP流的开始时间戳,就可以得到帧的真实时间戳。
以下代码为检索pts,dts实例代码:

// Grab a frame from the input stream
Frame frame = grabber.grab();
if (frame != null) {
  // Get the packet associated with the frame
  AVPacket packet = frame.opaque;
  
  // Get the packet's pts or dts timestamp (whichever is available)
  long timestamp = packet.pts != AV_NOPTS_VALUE ? packet.pts : packet.dts;
  
  // Convert the timestamp from the packet's time base to seconds
  double timestampInSeconds = timestamp * av_q2d(packet.time_base);
}



参考GPT和自我理解,思路如下:
您可以使用JavaCV中的FFmpegFrameGrabber来获取RTSP流中每一帧的时间戳。这个时间戳是相对于第一帧的时间戳,因此您可以通过把它与第一帧的时间戳相加来得到绝对时间戳。
示例代码片段:


FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("rtsp://your_rtsp_stream_url");
grabber.start();

long firstTimestamp = 0; // 第一帧时间戳
long currentTimestamp = 0; // 当前帧时间戳

while (true) {
    Frame frame = grabber.grab();
    if (frame == null) {
        break;
    }

    if (firstTimestamp == 0) { // 如果是第一帧
        firstTimestamp = frame.timestamp;
    }

    currentTimestamp = frame.timestamp;
    
    // 计算绝对时间戳
    long absoluteTimestamp = firstTimestamp + currentTimestamp;

    // 在这里处理您的代码逻辑,并使用absoluteTimestamp作为需要的绝对时间戳
}

grabber.stop();

请注意,由于网络延迟和音视频处理等因素,实际上获取到的时间戳可能与真实时间存在偏差。

该回答引用ChatGPT GPT-4

1、引入所需的依赖项(Maven为例):

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5.7</version>
</dependency>

2、使用JavaCV从RTSP流获取AVPacket:

import org.bytedeco.ffmpeg.avcodec.*;
import org.bytedeco.ffmpeg.avformat.*;
import org.bytedeco.ffmpeg.avutil.*;
import org.bytedeco.javacv.*;

import static org.bytedeco.ffmpeg.global.avcodec.*;
import static org.bytedeco.ffmpeg.global.avformat.*;
import static org.bytedeco.ffmpeg.global.avutil.*;

public class RTSPStream {
    public static void main(String[] args) {
        AVFormatContext inputContext = new AVFormatContext(null);
        int ret;

        avformat_network_init();

        // 打开RTSP流
        String rtspUrl = "rtsp://your_rtsp_url";
        if ((ret = avformat_open_input(inputContext, rtspUrl, null, null))
        String rtspUrl = "rtsp://your_rtsp_url";
        if ((ret = avformat_open_input(inputContext, rtspUrl, null, null)) < 0) {
            System.out.println("Error opening input");
            return;
        }

        // 获取流信息
        if (avformat_find_stream_info(inputContext, (AVDictionary) null) < 0) {
            System.out.println("Error finding stream info");
            return;
        }

        // 遍历所有流,找到视频流
        int videoStreamIndex = -1;
        for (int i = 0; i < inputContext.nb_streams(); i++) {
            if (inputContext.streams(i).codecpar().codec_type() == AVMEDIA_TYPE_VIDEO) {
                videoStreamIndex = i;
                break;
            }
        }

        if (videoStreamIndex == -1) {
            System.out.println("No video stream found");
            return;
        }

        // 获取视频流
        AVStream videoStream = inputContext.streams(videoStreamIndex);

        // 循环读取AVPacket并处理
        AVPacket packet = av_packet_alloc();
        while (av_read_frame(inputContext, packet) >= 0) {
                      if (packet.stream_index() == videoStreamIndex) {
                // 获取原始PTS和DTS
                long rawPts = packet.pts();
                long rawDts = packet.dts();

                // 将原始PTS和DTS转换为可读格式(例如,以秒为单位)
                double timeBase = av_q2d(videoStream.time_base());
                double ptsInSeconds = rawPts * timeBase;
                double dtsInSeconds = rawDts * timeBase;

                // 输出时间戳信息
                System.out.printf("PTS: %f, DTS: %f\n", ptsInSeconds, dtsInSeconds);

                // 处理AVPacket的其它部分,例如解码、存储等
            }

            // 释放AVPacket资源
            av_packet_unref(packet);
        }

        // 释放资源
        av_packet_free(packet);
        avformat_close_input(inputContext);
        avformat_network_deinit();

  

回答引用ChatGPT:
1 您可以通过以下方式获取 RTSP 流的真实时间戳:

获取 RTSP 流的帧速率(即每秒钟传输的帧数)和起始时间戳。您可以使用 FFmpeg 命令 ffprobe 获取这些信息。例如,以下命令将返回 RTSP 流的基本信息:

ffprobe -v quiet -print_format json -show_streams rtsp://example.com/stream

2 使用帧速率和起始时间戳计算每一帧的绝对时间戳。例如,假设 RTSP 流的起始时间戳为 1600000000,帧速率为 30 帧每秒,当前帧的 pts 为 3000,则当前帧的绝对时间戳为:

1600000000 + (3000 / 30) = 1600001000

3 因此,您可以使用上面的公式来计算每一帧的绝对时间戳。

在 JavaCV 中,您可以使用 av_q2d() 函数将时间戳从 AVStream.time_base 转换为秒。例如,以下代码将计算出从起始时间戳到当前帧的时间(以秒为单位):

AVPacket pkt = new AVPacket();
// 从 RTSP 流中读取一帧
if (av_read_frame(ic, pkt) >= 0) {
    AVStream stream = ic.streams(pkt.stream_index());
    AVRational timeBase = stream.time_base();
    long pts = pkt.pts();
    double timestamp = pts * av_q2d(timeBase);
    // 计算当前帧的绝对时间戳
    double absoluteTimestamp = startTime + timestamp;
}

其中,ic 是 AVFormatContext 对象,代表了输入流的上下文信息。startTime 是起始时间戳,可以从 ffprobe 命令中获取。

需要注意的是,AVPacket 中的时间戳是相对时间戳,它的单位是 AVStream.time_base。因此,在计算绝对时间戳之前,您需要将时间戳转换为以秒为单位的时间戳。在上面的代码中,我们使用了 av_q2d() 函数来进行时间戳转换。

最后,您可以将绝对时间戳用作您需要的时间戳。如果您需要将绝对时间戳转换为其他时间格式,可以使用 Java 提供的时间库(如 java.time)。

该回答引用ChatGPT
在使用javacv ffmpeg拉取rtsp流时,可以通过以下步骤获取到绝对时间戳:
1. 获取视频流的时间基准
在ffmpeg中,每个流都有一个时间基准,用于将时间戳转换为实际时间。可以通过以下代码获取视频流的时间基准:
AVStream* videoStream = pFormatCtx->streams[videoStreamIndex];
AVRational timeBase = videoStream->time_base;
其中,videoStreamIndex为视频流的索引,pFormatCtx为AVFormatContext结构体,time_base为时间基准。
2. 获取帧的时间戳
在获取每一帧时,可以通过以下代码获取帧的时间戳:
AVPacket packet;
av_read_frame(pFormatCtx, &packet);
AVStream* stream = pFormatCtx->streams[packet.stream_index];
int64_t pts = av_rescale_q(packet.pts, stream->time_base, AV_TIME_BASE_Q);
其中,packet为AVPacket结构体,stream为视频流,pts为帧的时间戳。
3. 计算绝对时间戳
通过以上步骤,我们已经获取到了视频流的时间基准和帧的时间戳,接下来就可以通过以下代码计算出绝对时间戳:
int64_t absoluteTimestamp = av_rescale_q(pts, AV_TIME_BASE_Q, timeBase);
其中,timeBase为视频流的时间基准,pts为帧的时间戳,absoluteTimestamp为绝对时间戳。
完整代码示例:
AVStream* videoStream = pFormatCtx->streams[videoStreamIndex];
AVRational timeBase = videoStream->time_base;
AVPacket packet;
av_read_frame(pFormatCtx, &packet);
AVStream* stream = pFormatCtx->streams[packet.stream_index];
int64_t pts = av_rescale_q(packet.pts, stream->time_base, AV_TIME_BASE_Q);
int64_t absoluteTimestamp = av_rescale_q(pts, AV_TIME_BASE_Q, timeBase);
printf("Absolute timestamp: %lld\
", absoluteTimestamp);

以下答案由GPT-3.5大模型与博主波罗歌共同编写:
在使用Javacv拉取rtsp流时,获取真实时间戳的方法如下:

  1. 首先需要引入Javacv库
import org.bytedeco.javacpp.opencv_core.*;
import org.bytedeco.javacpp.avformat.*;
import org.bytedeco.javacpp.avcodec.*;
import org.bytedeco.javacpp.avutil.*;
import org.bytedeco.javacpp.swscale.*;
  1. 定义一些全局变量
AVFormatContext inputContext;
AVCodecContext decoderContext;
AVFrame inputFrame, outputFrame;
AVStream inputStream;
AVPacket inputPacket;
SwigCallback allocatedFrameDimensionsCallback;
SwigCallback allocatedFrameCallback;

int videoStreamIndex;
int64_t firstPts, lastPts, ptsDiff;
int64_t firstDts, lastDts, dtsDiff;
int64_t startTime;
AVRational streamTimeBase, outputTimeBase;
  1. 编写初始化方法
public void init() {
   // 打开输入
   if (avformat_open_input(inputContext, inputFile.getAbsolutePath(), null, null) != 0) {
       System.out.println("Could not open input file " + inputFile);
       System.exit(1);
   }

   // 找到流信息
   if (avformat_find_stream_info(inputContext, (AVDictionary) null) < 0) {
       System.out.println("Could not find stream information");
       System.exit(1);
   }

   // 找到视频流
   videoStreamIndex = av_find_best_stream(inputContext, AVMEDIA_TYPE_VIDEO, -1, -1, null, 0);

   if (videoStreamIndex < 0) {
       System.out.println("Could not find video stream in the input file");
       System.exit(1);
   }

   // 获取流信息
   inputStream = inputContext.streams(videoStreamIndex);
   streamTimeBase = inputStream.time_base();

   // 打印相关信息
   av_dump_format(inputContext, 0, inputFile.getAbsolutePath(), 0);

   // 查找解码器
   AVCodec codec = avcodec_find_decoder(inputStream.codecpar().codec_id());
   decoderContext = avcodec_alloc_context3(codec);
   
   // 设置解码器参数
   if (avcodec_parameters_to_context(decoderContext, inputStream.codecpar()) < 0) {
       System.out.println("Failed to copy codec parameters to decoder context");
       System.exit(1);
   }

   // 打开解码器
   if (avcodec_open2(decoderContext, codec, null) < 0) {
       System.out.println("Failed to open codec");
       System.exit(1);
   }

   // 初始化输入帧
   inputFrame = av_frame_alloc();
   if (inputFrame == null) {
       System.out.println("Could not allocate input frame");
       System.exit(1);
   }

   // 初始化输出帧
   outputFrame = av_frame_alloc();
   if (outputFrame == null) {
       System.out.println("Could not allocate output frame");
       System.exit(1);
   }

   // 初始化输出包裹
   av_init_packet(inputPacket);
   if (inputPacket == null) {
       System.out.println("Could not allocate input packet");
       System.exit(1);
   }

   // 分配空间,获得回调
   allocatedFrameDimensionsCallback = new SwigCallback() {
        public long getCallback() {
            return frameAllocatedDimensions();
        }
   };

   allocatedFrameCallback = new SwigCallback() {
        public long getCallback() {
            return frameAllocated();
        }
   };
}

private long frameAllocatedDimensions() {
    return avcodec_align_dimensions2(decoderContext, outputFrame.width(), outputFrame.height(), null);
}

private long frameAllocated() {
    return 0;
}
  1. 实现获取时间戳的方法
private long getAbsoluteTimestamp(AVPacket packet) {
    // 获取时间戳
    long pts = av_packet_get_pts(packet);
    long dts = av_packet_get_dts(packet);

    // 更新时间戳差
    if (firstPts == 0) {
        firstPts = pts;
    }
    lastPts = pts;
    ptsDiff = lastPts - firstPts;

    if (firstDts == 0) {
        firstDts = dts;
    }
    lastDts = dts;
    dtsDiff = lastDts - firstDts;

    if (startTime == 0) {
        startTime = System.currentTimeMillis();
    }

    // 获取流时间基,以及获取视频帧时间基
    AVRational avTs = packet.stream_index() == videoStreamIndex ? streamTimeBase : AV_TIME_BASE_Q;

    // 获取绝对时间戳(单位为毫秒)
    long time = (pts * avTs.den() * 1000) / (avTs.num() * AV_TIME_BASE);
    long absoluteTime = startTime + time;

    return absoluteTime;
}
  1. 调用获取时间戳的方法
while (av_read_frame(inputContext, inputPacket) >= 0) {
   // 如果包是视频包
   if (inputPacket.stream_index() == videoStreamIndex) {
       // 解码数据
       int response = avcodec_send_packet(decoderContext, inputPacket);
       while (response >= 0) {
           response = avcodec_receive_frame(decoderContext, inputFrame);
           if (response == AVERROR_EOF || response == AVERROR_EAGAIN()) {
               break;
           }
           if (response < 0) {
               System.out.println("Error decoding video frame");
               System.exit(1);
           }

           // 复制解码后的数据到输出帧
           av_image_copy(outputFrame.data(), outputFrame.linesize(), new PointerPointer<>(inputFrame.data()), inputFrame.linesize(), decoderContext.pix_fmt(),
                   decoderContext.width(), decoderContext.height());

           // 获取时间戳
           long absoluteTimestamp = getAbsoluteTimestamp(inputPacket);
       }
   }
   av_packet_unref(inputPacket);
}

完整代码示例:

import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.annotation.*;
import org.bytedeco.javacpp.avcodec.*;
import org.bytedeco.javacpp.avformat.*;
import org.bytedeco.javacpp.avutil.*;
import org.bytedeco.javacpp.postproc.*;
import org.bytedeco.javacpp.swresample.*;
import org.bytedeco.javacpp.swscale.*;

import java.io.*;

public class Test {
    private static File inputFile = new File("your_file_path");

    AVFormatContext inputContext;
    AVCodecContext decoderContext;
    AVFrame inputFrame, outputFrame;
    AVStream inputStream;
    AVPacket inputPacket;
    SwigCallback allocatedFrameDimensionsCallback;
    SwigCallback allocatedFrameCallback;

    int videoStreamIndex;
    int64_t firstPts, lastPts, ptsDiff;
    int64_t firstDts, lastDts, dtsDiff;
    int64_t startTime;
    AVRational streamTimeBase, outputTimeBase;

    public static void main(String[] args) {
        Test test = new Test();
        test.init();
        test.getAbsoluteTimestamp(test.inputPacket);
    }

    public void init() {
        // 初始化格式上下文
        inputContext = new AVFormatContext(null);

        // 打开输入
        if (avformat_open_input(inputContext, inputFile.getAbsolutePath(), null, null) != 0) {
            System.out.println("Could not open input file " + inputFile);
            System.exit(1);
        }

        // 找到流信息
        if (avformat_find_stream_info(inputContext, (AVDictionary) null) < 0) {
            System.out.println("Could not find stream information");
            System.exit(1);
        }

        // 找到视频流
        videoStreamIndex = av_find_best_stream(inputContext, AVMEDIA_TYPE_VIDEO, -1, -1, null, 0);

        if (videoStreamIndex < 0) {
            System.out.println("Could not find video stream in the input file");
            System.exit(1);
        }

        // 获取流信息
        inputStream = inputContext.streams(videoStreamIndex);
        streamTimeBase = inputStream.time_base();

        // 打印相关信息
        av_dump_format(inputContext, 0, inputFile.getAbsolutePath(), 0);

        // 查找解码器
        AVCodec codec = avcodec_find_decoder(inputStream.codecpar().codec_id());
        decoderContext = avcodec_alloc_context3(codec);
        if (decoderContext == null) {
            System.out.println("Could not allocate decoder context");
            System.exit(1);
        }

        // 设置解码器参数
        if (avcodec_parameters_to_context(decoderContext, inputStream.codecpar()) < 0) {
            System.out.println("Failed to copy codec parameters to decoder context");
            System.exit(1);
        }

        // 打开解码器
        if (avcodec_open2(decoderContext, codec, null) < 0) {
            System.out.println("Failed to open codec");
            System.exit(1);
        }

        // 初始化输入帧
        inputFrame = av_frame_alloc();
        if (inputFrame == null) {
            System.out.println("Could not allocate input frame");
            System.exit(1);
        }

        // 初始化输出帧
        outputFrame = av_frame_alloc();
        if (outputFrame == null) {
            System.out.println("Could not allocate output frame");
            System.exit(1);
        }

        // 初始化输出包裹
        inputPacket = new AVPacket();
        av_init_packet(inputPacket);
        if (inputPacket == null) {
            System.out.println("Could not allocate input packet");
            System.exit(1);
        }

        // 分配空间,获得回调
        allocatedFrameDimensionsCallback = new SwigCallback() {
            public long getCallback() {
                return frameAllocatedDimensions();
            }
        };

        allocatedFrameCallback = new SwigCallback() {
            public long getCallback() {
                return frameAllocated();
            }
        };
    }

    private long frameAllocatedDimensions() {
        return avcodec_align_dimensions2(decoderContext, outputFrame.width(), outputFrame.height(), null);
    }

    private long frameAllocated() {
        return 0;
    }

    private long getAbsoluteTimestamp(AVPacket packet) {
        // 获取时间戳
        long pts = av_packet_get_pts(packet);
        long dts = av_packet_get_dts(packet);

        // 更新时间戳差
        if (firstPts == 0) {
            firstPts = pts;
        }
        lastPts = pts;
        ptsDiff = lastPts - firstPts;

        if (firstDts == 0) {
            firstDts = dts;
        }
        lastDts = dts;
        dtsDiff = lastDts - firstDts;

        if (startTime == 0) {
            startTime = System.currentTimeMillis();
        }

        // 获取流时间基,以及获取视频帧时间基
        AVRational avTs = packet.stream_index() == videoStreamIndex ? streamTimeBase : AV_TIME_BASE_Q;

        // 获取绝对时间戳(单位为毫秒)
        long time = (pts * avTs.den() * 1000) / (avTs.num() * AV_TIME_BASE);
        long absoluteTime = startTime + time;

        return absoluteTime;
    }
}

如果我的回答解决了您的问题,请采纳!

  • 在使用 javacv 和 ffmpeg 拉取 RTSP 流时,获取到的时间戳是相对时间戳,而非绝对时间戳。因此需要将相对时间戳转换成绝对时间戳来获取真实的时间戳。

要将相对时间戳转换成绝对时间戳,需要获取视频流的起始时间戳,也就是基准时间戳。通常情况下可以通过 FFmpeg 的 av_find_stream_info() 函数获取到媒体文件的码流信息,在其中可以找到视频流的起始时间戳(AVStream.start_time),它表示时间戳的起始值。

然后可以通过计算当前帧的时间戳与基准时间戳之间的差值,得到当前帧的绝对时间戳。示例代码如下:

import static org.bytedeco.ffmpeg.global.avcodec.*;
import static org.bytedeco.ffmpeg.global.avformat.*;
import static org.bytedeco.ffmpeg.global.avutil.*;

public class Main {
    public static void main(String[] args) {
        AVFormatContext formatContext = new AVFormatContext(null);
        int ret = avformat_open_input(formatContext, "rtsp://your.rtsp.url", null, null);
        if (ret < 0) {
            System.err.println("Failed to open input!");
            System.exit(1);
        }

        ret = avformat_find_stream_info(formatContext, null);
        if (ret < 0) {
            System.err.println("Failed to find stream info!");
            System.exit(1);
        }

        AVCodec codec = null;
        AVCodecParameters codecpar = null;
        AVStream stream = null;
        int videoStreamIndex = -1;

        for (int i = 0; i < formatContext.nb_streams(); i++) {
            AVStream currStream = formatContext.streams(i);
            codecpar = currStream.codecpar();
            codec = avcodec_find_decoder(codecpar.codec_id());
            if (codec == null) {
                continue;
            }
            if (codecpar.codec_type() == AVMEDIA_TYPE_VIDEO) {
                stream = currStream;
                videoStreamIndex = i;
                break;
            }
        }

        if (stream == null) {
            System.err.println("Failed to find video stream!");
            System.exit(1);
        }

        AVCodecContext codecContext = avcodec_alloc_context3(codec);
        if (codecContext == null) {
            System.err.println("Failed to allocate codec context!");
            System.exit(1);
        }

        ret = avcodec_parameters_to_context(codecContext, codecpar);
        if (ret < 0) {
            System.err.println("Failed to copy codec parameters to context!");
            System.exit(1);
        }

        ret = avcodec_open2(codecContext, codec, null);
        if (ret < 0) {
            System.err.println("Failed to open codec!");
            System.exit(1);
        }

        AVPacket packet = new AVPacket();
        av_init_packet(packet);

        long baseTimestamp = stream.start_time(); // 获取视频流的起始时间戳(基准时间戳)
        AVRational timeBase = stream.time_base();

        while (av_read_frame(formatContext, packet) >= 0) {
            if (packet.stream_index() != videoStreamIndex) {
                av_packet_unref(packet);
                continue;
            }

            AVFrame frame = av_frame_alloc();
            if (frame == null) {
                System.err.println("Failed to allocate frame!");
                System.exit(1);
            }

            ret = avcodec_send_packet(codecContext, packet);
            if (ret < 0) {
                System.err.println("Error sending packet to decoder!");
                System.exit(1);
            }

            while (ret >= 0) {
                ret = avcodec_receive_frame(codecContext, frame);
                if (ret == AVERROR_EOF || ret == AVERROR_EAGAIN) {
                    break;
                }
                if (ret < 0) {
                    System.err.println("Failed to receive frame from decoder!");
                    System.exit(1);
                }

                long dts = frame.best_effort_timestamp(); // 获取帧的 DTS (相对时间戳)
                long pts = av_frame_get_best_effort_timestamp(frame); // 获取帧的 PTS (相对时间戳)
                long duration = frame.pkt_duration(); // 获取帧的 duration (相对时间戳)

                // 计算当前帧的绝对时间戳
                long absoluteTimestamp = baseTimestamp + dts * (timeBase.num() * 1000000L / timeBase.den()); 

                // TODO: 处理当前帧,使用 absoluteTimestamp 作为当前帧的时间戳

                av_frame_unref(frame);
            }

            av_packet_unref(packet);
        }

        avcodec_free_context(codecContext);
        avformat_close_input(formatContext);
        avformat_free_context(formatContext);
    }
}

其中,AVStream.start_time 表示视频流的起始时间戳,AVStream.time_base 表示时间基准,用于将相对时间戳转换成绝对时间戳。注意,time_base 的分子表示 1 秒的时间单位数,分母表示每个时间单位有多少个基本时间单位,例如 time_base 是 1/90000,则表示 1 秒有 90000 个基本时间单位。

在上述代码中,请将"rtsp://your.rtsp.url"替换成你的 RTSP 视频流地址,然后根据自己的需求修改 TODO 部分的代码来处理当前帧。

基于new Bing 的精确模式的回答:

使用ffmpeg的av_rescale_q或av_rescale函数,将javacv获取的dts和pts从1000000为基转换为90000为基,这样就可以和rtp_timestamp对应起来。
使用ffmpeg的rtcp_parse_packet函数,获取rtcp_ntp_timestamp和rtcp_timestamp,这两个时间戳分别是绝对时间和相对时间,可以用来计算rtp_timestamp的偏移量。
使用ffmpeg的rtp_parse_packet_internal函数,获取rtp_timestamp,这个时间戳是网络传输时的时间基,可以用来同步音视频流。
希望这些方法能对你有所帮助。😊