searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

FFmpeg数据结构:AVPacket解析

2024-10-31 09:28:57
66
0

AVPacket是FFmpeg库中的一个核心数据结构,它主要用于存储从解复用器(demuxer)获取的压缩数据,这些数据在解码之前保持原样。同时,AVPacket还携带了与这些数据相关的元数据,如显示时间戳(PTS)、解码时间戳(DTS)、数据所属的媒体流索引等。下面,我将通过详细的解释和代码示例来说明AVPacket的使用。

AVPacket的结构与关键成员

AVPacket结构体在FFmpeg的libavcodec/avcodec.h头文件中定义,它包含了多个成员变量,以下是一些关键成员:

  • uint8_t *data:指向压缩数据的指针。
  • int size:压缩数据的大小(以字节为单位)。
  • int64_t pts:显示时间戳,表示数据包被提交给用户的时间点(以媒体流的时间基准为单位)。
  • int64_t dts:解码时间戳,表示数据包被解码的时间点(同样以媒体流的时间基准为单位)。
  • int stream_index:标识数据包所属的媒体流索引。
  • AVPacketSideData *side_data:指向附加数据的指针,这些附加数据由容器提供,可能包含关于数据包的额外信息。
  • int side_data_elems:附加数据的元素个数。
  • int64_t duration:数据包的时长(以媒体流的时间基准为单位)。
  • AVBufferRef *buf:用于管理数据缓存的引用计数。

AVPacket的使用流程

  1. 初始化AVPacket:在使用AVPacket之前,需要先对其进行初始化。这通常通过调用av_init_packet()函数来完成。
  2. 从解复用器获取数据:通过调用解复用器的相关函数(如av_read_frame()),可以从媒体文件中读取数据,并将这些数据存储在AVPacket中。
  3. 处理数据:根据需要对AVPacket中的数据进行处理,如解码、分析等。
  4. 释放AVPacket:当不再需要AVPacket时,应调用av_packet_unref()函数来释放其占用的资源。

代码示例

以下是一个简单的代码示例,展示了如何使用AVPacket从媒体文件中读取数据:

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/log.h>

void show_frame(const char *filepath) {
    av_register_all(); // 注册所有可用的格式和编解码器(在新版本的FFmpeg中可能已废弃)
    av_log_set_level(AV_LOG_DEBUG); // 设置日志级别

    AVFormatContext *formatContext = avformat_alloc_context(); // 分配格式上下文
    int status = 0;
    int success = 0;
    int videostreamidx = -1;
    AVCodecContext *codecContext = NULL;

    // 打开输入媒体文件
    status = avformat_open_input(&formatContext, filepath, NULL, NULL);
    if (status != 0) {
        av_log(NULL, AV_LOG_ERROR, "Could not open input file\n");
        return;
    }

    // 查找流信息
    status = avformat_find_stream_info(formatContext, NULL);
    if (status < 0) {
        av_log(NULL, AV_LOG_ERROR, "Could not find stream information\n");
        avformat_close_input(&formatContext);
        return;
    }

    // 查找视频流索引
    for (int i = 0; i < formatContext->nb_streams; i++) {
        if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videostreamidx = i;
            break;
        }
    }

    if (videostreamidx == -1) {
        av_log(NULL, AV_LOG_ERROR, "Could not find a video stream\n");
        avformat_close_input(&formatContext);
        return;
    }

    // 获取视频流和编解码器上下文
    AVStream *avstream = formatContext->streams[videostreamidx];
    codecContext = avstream->codec;
    AVCodec *codec = avcodec_find_decoder(codecContext->codec_id);

    if (!codec) {
        av_log(NULL, AV_LOG_ERROR, "Could not find codec\n");
        avformat_close_input(&formatContext);
        return;
    }

    // 打开编解码器
    status = avcodec_open2(codecContext, codec, NULL);
    if (status != 0) {
        av_log(NULL, AV_LOG_ERROR, "Could not open codec\n");
        avformat_close_input(&formatContext);
        return;
    }

    success = 1;

    if (success) {
        // 分配AVFrame并读取帧
        AVFrame *frame = av_frame_alloc();
        int decodelen = 0;
        int limitcount = 10; // 读取的帧数限制
        int pcindex = 0;

        while (pcindex < limitcount) {
            AVPacket packet;
            av_init_packet(&packet); // 初始化AVPacket

            // 从格式上下文中读取一个包
            status = av_read_frame(formatContext, &packet);
            if (status < 0) {
                if (status == AVERROR_EOF) {
                    av_log(NULL, AV_LOG_INFO, "End of file reached\n");
                } else {
                    av_log(NULL, AV_LOG_ERROR, "Error reading frame\n");
                }
                break;
            }

            // 检查包是否属于视频流
            if (packet.stream_index == videostreamidx) {
                // 解码视频帧
                decodelen = avcodec_decode_video2(codecContext, frame, &gotframe, &packet);
                if (decodelen > 0) {
                    // 处理解码后的帧(此处省略)
                    av_log(NULL, AV_LOG_DEBUG, "Decoded one frame, pcindex=%d\n", pcindex);
                }
            }

            // 释放AVPacket
            av_packet_unref(&packet);

            pcindex++;
        }

        // 释放AVFrame
        av_frame_free(&frame);
    }

    // 关闭编解码器和格式上下文
    avcodec_close(codecContext);
    avformat_close_input(&formatContext);
    avformat_free_context(formatContext);
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <input file>\n", argv[0]);
        return 1;
    }

    show_frame(argv[1]);
    return 0;
}

注意事项

  1. 在新版本的FFmpeg中,av_register_all()函数可能已被废弃,因为FFmpeg现在会自动注册所有可用的格式和编解码器。因此,在编写新代码时,可以省略此函数调用。
  2. 在处理AVPacket时,务必注意内存管理。特别是在释放AVPacket之前,应确保已经完成了对其中数据的所有处理。
  3. 示例代码中的错误处理部分较为简单,仅用于演示。在实际应用中,应添加更详细的错误处理逻辑以确保程序的健壮性。

通过以上解释和代码示例,相信您对AVPacket的使用有了更深入的了解。

0条评论
0 / 1000
怪****叔
4文章数
0粉丝数
怪****叔
4 文章 | 0 粉丝
怪****叔
4文章数
0粉丝数
怪****叔
4 文章 | 0 粉丝
原创

FFmpeg数据结构:AVPacket解析

2024-10-31 09:28:57
66
0

AVPacket是FFmpeg库中的一个核心数据结构,它主要用于存储从解复用器(demuxer)获取的压缩数据,这些数据在解码之前保持原样。同时,AVPacket还携带了与这些数据相关的元数据,如显示时间戳(PTS)、解码时间戳(DTS)、数据所属的媒体流索引等。下面,我将通过详细的解释和代码示例来说明AVPacket的使用。

AVPacket的结构与关键成员

AVPacket结构体在FFmpeg的libavcodec/avcodec.h头文件中定义,它包含了多个成员变量,以下是一些关键成员:

  • uint8_t *data:指向压缩数据的指针。
  • int size:压缩数据的大小(以字节为单位)。
  • int64_t pts:显示时间戳,表示数据包被提交给用户的时间点(以媒体流的时间基准为单位)。
  • int64_t dts:解码时间戳,表示数据包被解码的时间点(同样以媒体流的时间基准为单位)。
  • int stream_index:标识数据包所属的媒体流索引。
  • AVPacketSideData *side_data:指向附加数据的指针,这些附加数据由容器提供,可能包含关于数据包的额外信息。
  • int side_data_elems:附加数据的元素个数。
  • int64_t duration:数据包的时长(以媒体流的时间基准为单位)。
  • AVBufferRef *buf:用于管理数据缓存的引用计数。

AVPacket的使用流程

  1. 初始化AVPacket:在使用AVPacket之前,需要先对其进行初始化。这通常通过调用av_init_packet()函数来完成。
  2. 从解复用器获取数据:通过调用解复用器的相关函数(如av_read_frame()),可以从媒体文件中读取数据,并将这些数据存储在AVPacket中。
  3. 处理数据:根据需要对AVPacket中的数据进行处理,如解码、分析等。
  4. 释放AVPacket:当不再需要AVPacket时,应调用av_packet_unref()函数来释放其占用的资源。

代码示例

以下是一个简单的代码示例,展示了如何使用AVPacket从媒体文件中读取数据:

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/log.h>

void show_frame(const char *filepath) {
    av_register_all(); // 注册所有可用的格式和编解码器(在新版本的FFmpeg中可能已废弃)
    av_log_set_level(AV_LOG_DEBUG); // 设置日志级别

    AVFormatContext *formatContext = avformat_alloc_context(); // 分配格式上下文
    int status = 0;
    int success = 0;
    int videostreamidx = -1;
    AVCodecContext *codecContext = NULL;

    // 打开输入媒体文件
    status = avformat_open_input(&formatContext, filepath, NULL, NULL);
    if (status != 0) {
        av_log(NULL, AV_LOG_ERROR, "Could not open input file\n");
        return;
    }

    // 查找流信息
    status = avformat_find_stream_info(formatContext, NULL);
    if (status < 0) {
        av_log(NULL, AV_LOG_ERROR, "Could not find stream information\n");
        avformat_close_input(&formatContext);
        return;
    }

    // 查找视频流索引
    for (int i = 0; i < formatContext->nb_streams; i++) {
        if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videostreamidx = i;
            break;
        }
    }

    if (videostreamidx == -1) {
        av_log(NULL, AV_LOG_ERROR, "Could not find a video stream\n");
        avformat_close_input(&formatContext);
        return;
    }

    // 获取视频流和编解码器上下文
    AVStream *avstream = formatContext->streams[videostreamidx];
    codecContext = avstream->codec;
    AVCodec *codec = avcodec_find_decoder(codecContext->codec_id);

    if (!codec) {
        av_log(NULL, AV_LOG_ERROR, "Could not find codec\n");
        avformat_close_input(&formatContext);
        return;
    }

    // 打开编解码器
    status = avcodec_open2(codecContext, codec, NULL);
    if (status != 0) {
        av_log(NULL, AV_LOG_ERROR, "Could not open codec\n");
        avformat_close_input(&formatContext);
        return;
    }

    success = 1;

    if (success) {
        // 分配AVFrame并读取帧
        AVFrame *frame = av_frame_alloc();
        int decodelen = 0;
        int limitcount = 10; // 读取的帧数限制
        int pcindex = 0;

        while (pcindex < limitcount) {
            AVPacket packet;
            av_init_packet(&packet); // 初始化AVPacket

            // 从格式上下文中读取一个包
            status = av_read_frame(formatContext, &packet);
            if (status < 0) {
                if (status == AVERROR_EOF) {
                    av_log(NULL, AV_LOG_INFO, "End of file reached\n");
                } else {
                    av_log(NULL, AV_LOG_ERROR, "Error reading frame\n");
                }
                break;
            }

            // 检查包是否属于视频流
            if (packet.stream_index == videostreamidx) {
                // 解码视频帧
                decodelen = avcodec_decode_video2(codecContext, frame, &gotframe, &packet);
                if (decodelen > 0) {
                    // 处理解码后的帧(此处省略)
                    av_log(NULL, AV_LOG_DEBUG, "Decoded one frame, pcindex=%d\n", pcindex);
                }
            }

            // 释放AVPacket
            av_packet_unref(&packet);

            pcindex++;
        }

        // 释放AVFrame
        av_frame_free(&frame);
    }

    // 关闭编解码器和格式上下文
    avcodec_close(codecContext);
    avformat_close_input(&formatContext);
    avformat_free_context(formatContext);
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <input file>\n", argv[0]);
        return 1;
    }

    show_frame(argv[1]);
    return 0;
}

注意事项

  1. 在新版本的FFmpeg中,av_register_all()函数可能已被废弃,因为FFmpeg现在会自动注册所有可用的格式和编解码器。因此,在编写新代码时,可以省略此函数调用。
  2. 在处理AVPacket时,务必注意内存管理。特别是在释放AVPacket之前,应确保已经完成了对其中数据的所有处理。
  3. 示例代码中的错误处理部分较为简单,仅用于演示。在实际应用中,应添加更详细的错误处理逻辑以确保程序的健壮性。

通过以上解释和代码示例,相信您对AVPacket的使用有了更深入的了解。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0