音视频系列四:ffmpeg之获取音视频帧数据

简介: 音视频系列四:ffmpeg之获取音视频帧数据

一、AVFrame解码视频


1.先贴一个ffmpeg解析flv文件20帧数据后的截图,AVFrame是包含码流参数较多的结构体,结构体源码位于libavcodec/avcodec.h中


2.png


完整代码:


#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
int openCodecContext(const AVFormatContext* pFormatCtx, int* pStreamIndex, enum AVMediaType type, AVCodecContext** ppCodecCtx) {
    int streamIdx = -1;
    // 获取流下标
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == type) {
            streamIdx = i;
            break;
        }
    }
    if (streamIdx == -1) {
        printf("find video stream failed!\n");
        exit(-1);
    }
    // 寻找解码器
    AVCodecContext* pCodecCtx = pFormatCtx->streams[streamIdx]->codec;
    AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (NULL == pCodec) {
        printf("avcode find decoder failed!\n");
        exit(-1);
    }
    //打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        printf("avcode open failed!\n");
        exit(-1);
    }
    *ppCodecCtx = pCodecCtx;
    *pStreamIndex = streamIdx;
    return 0;
}
int main(void)
{
    AVFormatContext* pInFormatCtx = NULL;
    AVCodecContext* pVideoCodecCtx = NULL;
    AVCodecContext* pAudioCodecCtx = NULL;
    AVPacket* pPacket = NULL;
    AVFrame* pFrame = NULL;
    int ret;
    /* 支持本地文件和网络url */
    const char streamUrl[] = "./ouput_1min.flv";
    /* 1. 注册 */
    av_register_all();
    pInFormatCtx = avformat_alloc_context();
    /* 2. 打开流 */
    if (avformat_open_input(&pInFormatCtx, streamUrl, NULL, NULL) != 0) {
        printf("Couldn't open input stream.\n");
        return -1;
    }
    /* 3. 获取流的信息 */
    if (avformat_find_stream_info(pInFormatCtx, NULL) < 0) {
        printf("Couldn't find stream information.\n");
        return -1;
    }
    int videoStreamIdx = -1;
    int audioStreamIdx = -1;
    /* 4. 寻找并打开解码器 */
    openCodecContext(pInFormatCtx, &videoStreamIdx, AVMEDIA_TYPE_VIDEO, &pVideoCodecCtx);
    openCodecContext(pInFormatCtx, &audioStreamIdx, AVMEDIA_TYPE_AUDIO, &pAudioCodecCtx);
    pPacket = av_packet_alloc();
    pFrame = av_frame_alloc();
    int cnt = 20; // 读取20帧数据(音频和视频)
    while (cnt--) {
        /* 5. 读流数据, 未解码的数据存放于pPacket */
        ret = av_read_frame(pInFormatCtx, pPacket);
        if (ret < 0) {
            printf("av_read_frame error\n");
            break;
        }
        /* 6. 解码, 解码后的数据存放于pFrame */
        /* 视频解码 */
        if (pPacket->stream_index == videoStreamIdx) {
            avcodec_decode_video2(pVideoCodecCtx, pFrame, &ret, pPacket);
            if (ret == 0) {
                printf("video decodec error!\n");
                continue;
            }
            printf("* * * * * * video * * * * * * * * *\n");
            printf("___height: [%d]\n", pFrame->height);
            printf("____width: [%d]\n", pFrame->width);
            printf("pict_type: [%d]\n", pFrame->pict_type);
            printf("key_frame: [%d]\n", pFrame->key_frame); // 视频关键帧  1 -> 是 0 -> 否
            printf("___format: [%d]\n", pFrame->format);
            printf("* * * * * * * * * * * * * * * * * * *\n\n");
        }
        /* 音频解码 */
        if (pPacket->stream_index == audioStreamIdx) {
            avcodec_decode_audio4(pAudioCodecCtx, pFrame, &ret, pPacket);
            if (ret < 0) {
                printf("audio decodec error!\n");
                continue;
            }
            printf("* * * * * * audio * * * * * * * * * *\n");
            printf("____nb_samples: [%d]\n", pFrame->nb_samples);
            printf("__samples_rate: [%d]\n", pFrame->sample_rate);
            printf("channel_layout: [%lu]\n", pFrame->channel_layout);
            printf("________format: [%d]\n", pFrame->format);
            printf("* * * * * * * * * * * * * * * * * * *\n\n");
        }
        av_packet_unref(pPacket); /* 将缓存空间的引用计数-1,并将Packet中的其他字段设为初始值。如果引用计数为0,自动的释放缓存空间 */
    }
    /* 释放资源 */
    av_frame_free(&pFrame);
    av_packet_free(&pPacket);
    avcodec_close(pVideoCodecCtx);
    avcodec_close(pAudioCodecCtx);
    avformat_close_input(&pInFormatCtx);
    return 0;
}


2.简单介绍一下流程中的各个函数的意义:


av_register_all():注册FFmpeg所有编解码器。


avformat_open_input():打开流的AVFormatContext。


avformat_find_stream_info():获取流的信息。


avcodec_find_encoder():查找编码器。


avcodec_open2():打开编码器。


av_read_frame():读流数据。


avcodec_decode_video2():视频解码。


av_write_frame():将编码后的视频码流写入文件。


av_packet_unref():将缓存空间的引用计数-1,并将Packet中的其他字段设为初始值。如果引用计数为0,自动的释放缓存空间。


二、AVFrame 数据结构


AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。


这里源码的注释太冗长所以省略了。


typedef struct AVFrame {
#define AV_NUM_DATA_POINTERS 8
    uint8_t *data[AV_NUM_DATA_POINTERS];
    int linesize[AV_NUM_DATA_POINTERS];
    uint8_t **extended_data;
    int width, height;
    int nb_samples;
    int format;
    int key_frame;
    enum AVPictureType pict_type;
    AVRational sample_aspect_ratio;
    int64_t pts;
#if FF_API_PKT_PTS
    attribute_deprecated
    int64_t pkt_pts;
#endif
    int64_t pkt_dts;
    int coded_picture_number;
    int display_picture_number;
    int quality;
    void *opaque;
#if FF_API_ERROR_FRAME
    attribute_deprecated
    uint64_t error[AV_NUM_DATA_POINTERS];
#endif
    int repeat_pict;
    int interlaced_frame;
    int top_field_first;
    int palette_has_changed;
    int64_t reordered_opaque;
    int sample_rate;
    uint64_t channel_layout;
    AVBufferRef *buf[AV_NUM_DATA_POINTERS];
    AVBufferRef **extended_buf;
    int        nb_extended_buf;
    AVFrameSideData **side_data;
    int            nb_side_data;
#define AV_FRAME_FLAG_CORRUPT       (1 << 0)
#define AV_FRAME_FLAG_DISCARD   (1 << 2)
    int flags;
    enum AVColorRange color_range;
    enum AVColorPrimaries color_primaries;
    enum AVColorTransferCharacteristic color_trc;
    enum AVColorSpace colorspace;
    enum AVChromaLocation chroma_location;
    int64_t best_effort_timestamp;
    int64_t pkt_pos;
    int64_t pkt_duration;
    AVDictionary *metadata;
    int decode_error_flags;
#define FF_DECODE_ERROR_INVALID_BITSTREAM   1
#define FF_DECODE_ERROR_MISSING_REFERENCE   2
#define FF_DECODE_ERROR_CONCEALMENT_ACTIVE  4
#define FF_DECODE_ERROR_DECODE_SLICES       8
    int channels;
    int pkt_size;
#if FF_API_FRAME_QP
    attribute_deprecated
    int8_t *qscale_table;
    attribute_deprecated
    int qstride;
    attribute_deprecated
    int qscale_type;
    attribute_deprecated
    AVBufferRef *qp_table_buf;
#endif
    AVBufferRef *hw_frames_ctx;
    AVBufferRef *opaque_ref;
    size_t crop_top;
    size_t crop_bottom;
    size_t crop_left;
    size_t crop_right;
    AVBufferRef *private_ref;
    } AVFrame;


接下来集中看常用的一些结构成员:


2.1 data


uint8_t *data[AV_NUM_DATA_POINTERS]; // 解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)


data 是一个指针数组,数组的每一个元素是一个指针,指向视频中图像的某一 plane 或音频中某一声道的 plane。


2.2 linesize


int linesize[AV_NUM_DATA_POINTERS]; // data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽


对于视频来说,linesize 每个元素是一个图像 plane 中一行图像的大小(字节数)。注意有对齐要求


对于音频来说,linesize 每个元素是一个音频 plane 的大小(字节数)


linesize 可能会因性能上的考虑而填充一些额外的数据,因此 linesize 可能比实际对应的音视频数据尺寸要大。


2.3 width, height;


int width, height; // 视频帧宽和高(1920x1080,1280x720...)


2.4 nb_samples


int nb_samples; // 音频帧中单个声道中包含的采样点数。


2.5 format


int format; // 解码后原始数据类型


对于视频帧,此值对应于enum AVPixelFormat


enum AVPixelFormat {  
    AV_PIX_FMT_NONE = -1,  
    AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)  
    AV_PIX_FMT_YUYV422,   ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr  
    AV_PIX_FMT_RGB24,     ///< packed RGB 8:8:8, 24bpp, RGBRGB...  
    AV_PIX_FMT_BGR24,     ///< packed RGB 8:8:8, 24bpp, BGRBGR...  
    AV_PIX_FMT_YUV422P,   ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)  
    AV_PIX_FMT_YUV444P,   ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples)  
    AV_PIX_FMT_YUV410P,   ///< planar YUV 4:1:0,  9bpp, (1 Cr & Cb sample per 4x4 Y samples)  
    AV_PIX_FMT_YUV411P,   ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples)  
    AV_PIX_FMT_GRAY8,     ///<        Y        ,  8bpp  
    AV_PIX_FMT_MONOWHITE, ///<        Y        ,  1bpp, 0 is white, 1 is black, in each byte pixels are ordered from the msb to the lsb  
    AV_PIX_FMT_MONOBLACK, ///<        Y        ,  1bpp, 0 is black, 1 is white, in each byte pixels are ordered from the msb to the lsb  
    AV_PIX_FMT_PAL8,      ///< 8 bit with PIX_FMT_RGB32 palette  
    AV_PIX_FMT_YUVJ420P,  ///< planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of PIX_FMT_YUV420P and setting color_range
    ...(省略)  
}


对于音频帧,此值对应于enum AVSampleFormat


enum AVSampleFormat {  
    AV_SAMPLE_FMT_NONE = -1,  
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits  
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits  
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits  
    AV_SAMPLE_FMT_FLT,         ///< float  
    AV_SAMPLE_FMT_DBL,         ///< double  
    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar  
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar  
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar  
    AV_SAMPLE_FMT_FLTP,        ///< float, planar  
    AV_SAMPLE_FMT_DBLP,        ///< double, planar 
    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically  
};


2.6 key_frame


int key_frame; // 是否是关键帧


2.7 pict_type


enum AVPictureType pict_type; // 帧类型(I,B,P...)


视频帧类型(I、B、P 等)


enum AVPictureType {
    AV_PICTURE_TYPE_NONE = 0, ///< Undefined
    AV_PICTURE_TYPE_I,     ///< Intra
    AV_PICTURE_TYPE_P,     ///< Predicted
    AV_PICTURE_TYPE_B,     ///< Bi-dir predicted
    AV_PICTURE_TYPE_S,     ///< S(GMC)-VOP MPEG-4
    AV_PICTURE_TYPE_SI,    ///< Switching Intra
    AV_PICTURE_TYPE_SP,    ///< Switching Predicted
    AV_PICTURE_TYPE_BI,    ///< BI type
};


2.8 sample_aspect_ratio


AVRational sample_aspect_ratio; // 视频宽高比(16:9,4:3...)


2.9 pts


int64_t pts; // 显示时间戳 单位是 time_base


2.10 pkt_pts


int64_t pkt_pts;


此 frame 对应的 packet 中的解码时间戳。是从对应 packet(解码生成此 frame)中拷贝 DTS 得到此值。


如果对应的 packet 中只有 dts 而未设置 pts,则此值也是此 frame 的 pts。


2.11 coded_picture_number


int coded_picture_number; // 编码帧序号


2.12 display_picture_number


int display_picture_number; // 显示帧序号


2.13 interlaced_frame


int interlaced_frame; // 是否是隔行扫描


2.14 sample_rate


int sample_rate; // 音频采样率


2.15 buf


AVBufferRef *buf[AV_NUM_DATA_POINTERS];


此帧的数据可以由 AVBufferRef 管理,AVBufferRef 提供 AVBuffer 引用机制


AVBuffer 是 FFmpeg 中很常用的一种缓冲区,缓冲区使用引用计数(reference-counted)机制


2.16 pkt_pos


int64_t pkt_pos; // 最后一个扔进解码器的 packet 在输入文件中的位置偏移量


2.17 pkt_duration


int64_t pkt_duration;// 对应 packet 的时长,单位是 AVStream->time_base


2.18 channels


int channels;// 音频声道数量


2.19 pkt_size


int pkt_size;// 对应 packet 的大小


2.20 crop_


size_t crop_top;
size_t crop_bottom;
size_t crop_left;
size_t crop_right;


用于视频帧图像裁切。四个值分别为从 frame 的上/下/左/右边界裁切的像素数。


如果你觉得文章还不错,可以给个"三连"


我是加班猿,我们下期见

目录
相关文章
|
Web App开发 5G Linux
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
一年一度的毕业季来临,计算机专业的毕业设计尤为重要,不仅关乎学业评价还积累实战经验。选择紧跟5G技术趋势的音视频APP作为课题极具吸引力。这里推荐三类应用:一是融合WebRTC技术实现视频通话的即时通信APP;二是具备在线直播功能的短视频分享平台,涉及RTMP/SRT等直播技术;三是具有自定义动画特效及卡拉OK歌词字幕功能的视频剪辑工具。这些项目不仅技术含量高,也符合市场需求,是毕业设计的理想选择。
348 6
FFmpeg开发笔记(四十四)毕业设计可做的几个拉满颜值的音视频APP
|
数据采集 大数据 Python
FFmpeg 在爬虫中的应用案例:流数据解码详解
在大数据背景下,网络爬虫与FFmpeg结合,高效采集小红书短视频。需准备FFmpeg、Python及库如Requests和BeautifulSoup。通过设置User-Agent、Cookie及代理IP增强隐蔽性,解析HTML提取视频链接,利用FFmpeg下载并解码视频流。示例代码展示完整流程,强调代理IP对避免封禁的关键作用,助你掌握视频数据采集技巧。
376 7
FFmpeg 在爬虫中的应用案例:流数据解码详解
|
Android开发 计算机视觉 C++
FFmpeg开发笔记(五十一)适合学习研究的几个音视频开源框架
音视频编程对许多程序员来说是一片充满挑战的领域,但借助如OpenCV、LearnOpenGL、FFmpeg、OBS Studio及VLC media player等强大的开源工具,可以降低入门门槛。这些框架不仅覆盖了计算机视觉、图形渲染,还包括多媒体处理与直播技术,通过多种编程语言如Python、C++的应用,使得音视频开发更为便捷。例如,OpenCV支持跨平台的视觉应用开发,FFmpeg则擅长多媒体文件的处理与转换,而VLC media player则是验证音视频文件质量的有效工具。
579 0
FFmpeg开发笔记(五十一)适合学习研究的几个音视频开源框架
|
语音技术 C语言 Windows
语音识别------ffmpeg的使用01,ffmpeg的安装,会做PPT很好,ffmpeg不具备直接使用,只可以操作解码数据,ffmpeg用C语言写的,得学C语言,ffmpeg的安装
语音识别------ffmpeg的使用01,ffmpeg的安装,会做PPT很好,ffmpeg不具备直接使用,只可以操作解码数据,ffmpeg用C语言写的,得学C语言,ffmpeg的安装
|
达摩院 语音技术 异构计算
语音识别-免费开源的语音转文本软件Whisper的本地搭建详细教程,python版本是3.805,ffmpeg是专门处理音视频的,ffmpeg的下载链接,现在要求安装python和ffmpeg
语音识别-免费开源的语音转文本软件Whisper的本地搭建详细教程,python版本是3.805,ffmpeg是专门处理音视频的,ffmpeg的下载链接,现在要求安装python和ffmpeg
|
存储 编解码 Linux
rodert教你学FFmpeg实战这一篇就够了 - 音视频处理入门篇
rodert教你学FFmpeg实战这一篇就够了 - 音视频处理入门篇
326 1
C#进程调用FFmpeg操作音视频
因为公司需要对音视频做一些操作,比如说对系统用户的发音和背景视频进行合成,以及对多个音视频之间进行合成,还有就是在指定的源背景音频中按照对应的规则在视频的多少秒钟内插入一段客户发音等一些复杂的音视频操作。本篇文章主要讲解的是使用C#进程(Process)调用FFmpeg.exe进行视频合并、音频合并、音频与视频合并成视频这几个简单的音视频操作。
243 0
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
1453 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频