音视频入门基础:像素格式专题(3)——FFmpeg源码解析BMP格式图片的底层实现原理

=================================================================

音视频入门基础:像素格式专题系列文章:

音视频入门基础:像素格式专题(1)——RGB简介

音视频入门基础:像素格式专题(2)——不通过第三方库将RGB24格式视频转换为BMP格式图片

音视频入门基础:像素格式专题(3)——FFmpeg源码解析BMP格式图片的底层实现原理

音视频入门基础:像素格式专题(4)——YUV简介

音视频入门基础:像素格式专题(5)——使用OpenGL实现yuv420p转rgb播放视频(三重纹理实现)

=================================================================

一、引言

FFmpeg可以将图片或者媒体文件中的信息(比如图片或视频的分辨率,像素格式等信息)解析出来。本文以BMP格式的图片为例子,讲解FFmpeg解析图片的底层实现原理,通过本文也可以了解到FFmpeg的avformat_open_input函数和avformat_find_stream_info函数的内部实现原理。

二、通过FFmpeg命令获取BMP格式图片的信息

通过FFmpeg命令获取BMP格式图片的信息非常简单,仅需要执行命令:

ffmpeg -i XXX.bmp

即可得到BMP图片的信息。从上图中可以看到该图片的格式为bmp,像素格式为bgr24,分辨率为1280x720

三、通过FFmpeg api获取BMP格式图片的信息

Linux平台下使用C++调用FFmpeg api 编写获取BMP格式图片的信息。例子如下:

#include <iostream>

extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}

using namespace std;


int main()
{
        string strPath = "output_0.bmp";
        AVFormatContext *ic = NULL;    //AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体
        int re = avformat_open_input(&ic, strPath.c_str(), NULL, NULL);
        if (re != 0)  //如果打开媒体文件失败,打印失败原因。比如,如果上面没有调用函数av_register_all,则会打印“XXX failed!:Invaliddata found when processing input”
        {
                char buf[1024] = { 0 };
                av_strerror(re, buf, sizeof(buf) - 1);
                cout << "open " << strPath << " failed!: " << buf << endl;
        }
        else         //打开媒体文件成功
        {
                cout << "open " << strPath << " success!" << endl;
                avformat_find_stream_info(ic, NULL); //调用该函数可以进一步读取一部分视音频数据并且获得一些相关的信息。调用avformat_open_input之后,我们无法获取到正确和所有的媒体参数,所以还得要调用avformat_find_stream_info进一步的去获取。


                for (int i = 0; i < ic->nb_streams; i++) //通过遍历的方式读取媒体文件视频和音频的信息,新版本的FFmpeg新增加了函数av_find_best_stream,也可以取得同样的效果,但这里为了兼容旧版本还是用这种遍历的方式
                {
                        AVStream *as = ic->streams[i];
                        if (AVMEDIA_TYPE_VIDEO == as->codecpar->codec_type)  //如果是视频流,则打印视频的信息
                        {
                                cout << "图片宽度:" << as->codecpar->width << " 图片高度:" << as->codecpar->height << endl;

                AVCodec *codec =  (AVCodec *)avcodec_find_decoder(as->codecpar->codec_id);
                cout << "图片格式:" << codec->long_name << endl;

                AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);//需要使用avcodec_free_context释放
//事实上codecpar包含了大部分解码器相关的信息,这里是直接从AVCodecParameters复制到AVCodecContext
                avcodec_parameters_to_context(codec_ctx, as->codecpar);
                if(AV_PIX_FMT_BGR24 == codec_ctx->pix_fmt)
                {
                    cout << "像素格式:" << "BGR24" << endl;
                }
                //avcodec_open2(codec_ctx, codec, nullptr);
                if(codec_ctx)
                {
                    avcodec_free_context(&codec_ctx);
                }
                        }
                }

                //av_dump_format(ic, 0, path, 0);
        }
        if (ic)
        {
                avformat_close_input(&ic); //关闭一个AVFormatContext,和函数avformat_open_input()成对使用
        }
        return 0;
}

输出:

可以看到上述代码非常简单,流程跟FFmpeg对媒体文件进行解封装/解复用一样,仅需要调用avformat_open_input函数和avformat_find_stream_info函数。下面讲解FFmpeg源码获取BMP格式图片信息的底层实现原理。

四、FFmpeg源码解析BMP格式图片的底层实现原理

(一)打开BMP格式图片

执行avformat_open_input函数,首先内部会调用avpriv_open(const char *filename, int flags, ...)函数,打开需要被解析的BMP格式图片。

整个调用堆栈为:

err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts);    ->


if ((ret = init_input(s, filename, &tmp)) < 0)    ->


if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)    ->


return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);    ->


err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);   ->


ret = ffurl_connect(*puc, options);    ->


uc->prot->url_open(uc, uc->filename, uc->flags);   ->


fd = avpriv_open(filename, access, 0666);   ->
 

avpriv_open(const char *filename, int flags, ...)函数的源码为:

int avpriv_open(const char *filename, int flags, ...)
{
    int fd;
    unsigned int mode = 0;
    va_list ap;

    va_start(ap, flags);
    if (flags & O_CREAT)
        mode = va_arg(ap, unsigned int);
    va_end(ap);

#ifdef O_CLOEXEC
    flags |= O_CLOEXEC;
#endif
#ifdef O_NOINHERIT
    flags |= O_NOINHERIT;
#endif

    fd = open(filename, flags, mode);
#if HAVE_FCNTL
    if (fd != -1) {
        if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
            av_log(NULL, AV_LOG_DEBUG, "Failed to set close on exec\n");
    }
#endif

    return fd;
}

可以看到函数avpriv_open内部通过系统调用 fd = open(filename, flags, mode) 打开需要被解析的BMP图片,得到文件描述符fd。

通过VSCode调试FFmpeg源码,可以看到avformat_open_input函数的最底层确实执行了open函数。(关于用VSCode调试C/C++代码的教程可以参考《VsCode + gdb + gdbserver远程调试C++程序》)

(二)读取BMP格式图片的数据

然后函数avformat_open_input内部会调用函数file_read 读取BMP格式图片的内容。

整个调用堆栈为:

avformat_open_input ->
if ((ret = init_input(s, filename, &tmp)) < 0) ->

return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                  s, 0, s->format_probesize); ->
                                        
if ((ret = avio_read(pb, buf + buf_offset,
                             probe_size - buf_offset)) < 0)  ->
                             
fill_buffer(s);   ->

len = read_packet_wrapper(s, dst, len);  ->


int ffurl_read(URLContext *h, unsigned char *buf, int size) ->


return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read); ->


ret = transfer_func(h, buf + len, size - len); ->

file_read(URLContext *h, unsigned char *buf, int size)

file_read(URLContext *h, unsigned char *buf, int size)函数的源码为:

static int file_read(URLContext *h, unsigned char *buf, int size)
{
    FileContext *c = h->priv_data;
    int ret;
    size = FFMIN(size, c->blocksize);
    ret = read(c->fd, buf, size);
    if (ret == 0 && c->follow)
        return AVERROR(EAGAIN);
    if (ret == 0)
        return AVERROR_EOF;
    return (ret == -1) ? AVERROR(errno) : ret;
}

可以看到函数file_read内部通过系统调用 ret = read(c->fd, buf, size)读取BMP格式图片的内容,并将其保存到缓存区buf中。

通过VSCode调试FFmpeg源码,可以看到avformat_open_input函数的最底层确实执行了read函数。

(三)得到BMP格式图片在FFmpeg中的AVInputFormat结构

AVInputFormat是FFmpeg中的解复用器结构体,每种作为输入的封装格式(例如BMP、FLV、MP4、TS等)都对应一种AVInputFormat 结构。

以BMP这种图片的封装格式为例,它对应的AVInputFormat 结构为:

IMAGEAUTO_DEMUXER(bmp,     AV_CODEC_ID_BMP)

将该宏定义展开即为:

const AVInputFormat ff_image_bmp_pipe_demuxer = {
.name           = "bmp_pipe",
.long_name      = "piped bmp sequence",
.priv_data_size = 1208,
.read_probe     = bmp_probe,
.read_header    = ff_img_read_header,
.read_packet    = ff_img_read_packet,
.priv_class     = &imagepipe_class,
.flags          = 256,
.raw_codec_id   = AV_CODEC_ID_BMP,
};

具体可以参考:《FFmpeg源码:#define IMAGEAUTO_DEMUXER(imgname, codecid) 宏定义分析》

FFmpeg的avformat_open_input函数内部会根据媒体/图片封装格式的特点,通过一个while循环,对全部已知的格式进行判断,然后根据是否能被解析为该AVInputFormat对应的容器格式,来关联到对应的文件容器格式 ,这个while循环就是:while ((fmt1 = av_demuxer_iterate(&i))) 。

简单来讲,函数avformat_open_input内部会调用函数av_probe_input_format3,而后者是FFmpeg中实现媒体/图片格式探测的函数,该函数内部通过循环while ((fmt1 = av_demuxer_iterate(&i))) 拿到所有容器格式对应的AVInputFormat结构,然后通过score = fmt1->read_probe(&lpd)语句执行不同容器格式对应的解析函数,根据是否能被解析,来判断出这是哪种容器格式。

整个调用堆栈为:

avformat_open_input ->


if ((ret = init_input(s, filename, &tmp)) < 0) ->


return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                  s, 0, s->format_probesize); ->
                                 
*fmt = av_probe_input_format2(&pd, 1, &score); ->


const AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret); ->

av_probe_input_format3函数的源码为:

/**
 * Guess the file format.
 *
 * @param is_opened Whether the file is already opened; determines whether
 *                  demuxers with or without AVFMT_NOFILE are probed.
 * @param score_ret The score of the best detection.
 */
const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
                                            int is_opened, int *score_ret);

const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
                                            int is_opened, int *score_ret)
{
    AVProbeData lpd = *pd;
    const AVInputFormat *fmt1 = NULL;
    const AVInputFormat *fmt = NULL;
    int score, score_max = 0;
    void *i = 0;
    const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
    enum nodat {
        NO_ID3,
        ID3_ALMOST_GREATER_PROBE,
        ID3_GREATER_PROBE,
        ID3_GREATER_MAX_PROBE,
    } nodat = NO_ID3;

    if (!lpd.buf)
        lpd.buf = (unsigned char *) zerobuffer;

    if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
        int id3len = ff_id3v2_tag_len(lpd.buf);
        if (lpd.buf_size > id3len + 16) {
            if (lpd.buf_size < 2LL*id3len + 16)
                nodat = ID3_ALMOST_GREATER_PROBE;
            lpd.buf      += id3len;
            lpd.buf_size -= id3len;
        } else if (id3len >= PROBE_BUF_MAX) {
            nodat = ID3_GREATER_MAX_PROBE;
        } else
            nodat = ID3_GREATER_PROBE;
    }

    while ((fmt1 = av_demuxer_iterate(&i))) {
        if (fmt1->flags & AVFMT_EXPERIMENTAL)
            continue;
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
            continue;
        score = 0;
        if (fmt1->read_probe) {
            score = fmt1->read_probe(&lpd);
            if (score)
                av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
            if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
                switch (nodat) {
                case NO_ID3:
                    score = FFMAX(score, 1);
                    break;
                case ID3_GREATER_PROBE:
                case ID3_ALMOST_GREATER_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
                    break;
                case ID3_GREATER_MAX_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
                    break;
                }
            }
        } else if (fmt1->extensions) {
            if (av_match_ext(lpd.filename, fmt1->extensions))
                score = AVPROBE_SCORE_EXTENSION;
        }
        if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
            if (AVPROBE_SCORE_MIME > score) {
                av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME);
                score = AVPROBE_SCORE_MIME;
            }
        }
        if (score > score_max) {
            score_max = score;
            fmt       = fmt1;
        } else if (score == score_max)
            fmt = NULL;
    }
    if (nodat == ID3_GREATER_PROBE)
        score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
    *score_ret = score_max;

    return fmt;
}

下面以BMP格式图片为例子。调试FFmpeg源码,可以发现当i等于299时, AVInputFormat 类型的结构体变量fmt1即为BMP格式图片对应的AVInputFormat 结构,其成员变量name的值为 "bmp_pipe"。

这时候变量fmt1的值即为本文上面讲到的“(三)得到BMP格式图片在FFmpeg中的AVInputFormat结构”中的IMAGEAUTO_DEMUXER(bmp,     AV_CODEC_ID_BMP),也就是:

const AVInputFormat ff_image_bmp_pipe_demuxer = {
.name           = "bmp_pipe",
.long_name      = "piped bmp sequence",
.priv_data_size = 1208,
.read_probe     = bmp_probe,
.read_header    = ff_img_read_header,
.read_packet    = ff_img_read_packet,
.priv_class     = &imagepipe_class,
.flags          = 256,
.raw_codec_id   = AV_CODEC_ID_BMP,
};

这时候执行score = fmt1->read_probe(&lpd)语句,会执行回调函数bmp_probe(const AVProbeData *p),该函数即为BMP格式图片的探测函数,用来判断被打开的文件是否为BMP格式图片。

bmp_probe(const AVProbeData *p) 函数的源码为:

static int bmp_probe(const AVProbeData *p)
{
    const uint8_t *b = p->buf;
    int ihsize;

    if (AV_RB16(b) != 0x424d)
        return 0;

    ihsize = AV_RL32(b+14);
    if (ihsize < 12 || ihsize > 255)
        return 0;

    if (!AV_RN32(b + 6)) {
        return AVPROBE_SCORE_EXTENSION + 1;
    }
    return AVPROBE_SCORE_EXTENSION / 4;
}

可以很清楚的看到,函数bmp_probe是根据输入文件的第一、第二个字节是否为0x424d("BM")来判断其是否为BMP格式图片的:

BMP格式图片的header结构如下。其第一、第二个字节必须为0x424d :

关于BMP格式图片的header可以参考:《音视频入门基础:像素格式专题(2)——不通过第三方库将RGB24格式视频转换为BMP格式图片》

(四)解析BMP格式图片的信息

如果FFmpeg通过上一步判断出输入文件为BMP格式图片,则会执行

函数bmp_decode_frame(AVCodecContext *avctx,void *data, int *got_frame,AVPacket *avpkt)

 将图片中的信息解析出来。

整个调用堆栈为:

avformat_find_stream_info ->

try_decode_frame(ic, st, pkt,(options && i < orig_nb_streams) ? &options[i] : NULL); ->

ret = avcodec_send_packet(avctx, &pkt); ->

ret = decode_receive_frame_internal(avctx, avci->buffer_frame); ->

ret = decode_simple_receive_frame(avctx, frame); ->

ret = decode_simple_internal(avctx, frame, &discarded_samples); ->

ret = avctx->codec->decode(avctx, frame, &got_frame, pkt); ->

bmp_decode_frame(AVCodecContext *avctx,void *data, int *got_frame,AVPacket *avpkt)

函数bmp_decode_frame的源码为:

static int bmp_decode_frame(AVCodecContext *avctx,
                            void *data, int *got_frame,
                            AVPacket *avpkt)
{
    const uint8_t *buf = avpkt->data;
    int buf_size       = avpkt->size;
    AVFrame *p         = data;
    unsigned int fsize, hsize;
    int width, height;
    unsigned int depth;
    BiCompression comp;
    unsigned int ihsize;
    int i, j, n, linesize, ret;
    uint32_t rgb[3] = {0};
    uint32_t alpha = 0;
    uint8_t *ptr;
    int dsize;
    const uint8_t *buf0 = buf;
    GetByteContext gb;

    if (buf_size < 14) {
        av_log(avctx, AV_LOG_ERROR, "buf size too small (%d)\n", buf_size);
        return AVERROR_INVALIDDATA;
    }

    if (bytestream_get_byte(&buf) != 'B' ||
        bytestream_get_byte(&buf) != 'M') {
        av_log(avctx, AV_LOG_ERROR, "bad magic number\n");
        return AVERROR_INVALIDDATA;
    }

    fsize = bytestream_get_le32(&buf);
    if (buf_size < fsize) {
        av_log(avctx, AV_LOG_ERROR, "not enough data (%d < %u), trying to decode anyway\n",
               buf_size, fsize);
        fsize = buf_size;
    }

    buf += 2; /* reserved1 */
    buf += 2; /* reserved2 */

    hsize  = bytestream_get_le32(&buf); /* header size */
    ihsize = bytestream_get_le32(&buf); /* more header size */
    if (ihsize + 14LL > hsize) {
        av_log(avctx, AV_LOG_ERROR, "invalid header size %u\n", hsize);
        return AVERROR_INVALIDDATA;
    }

    /* sometimes file size is set to some headers size, set a real size in that case */
    if (fsize == 14 || fsize == ihsize + 14)
        fsize = buf_size - 2;

    if (fsize <= hsize) {
        av_log(avctx, AV_LOG_ERROR,
               "Declared file size is less than header size (%u < %u)\n",
               fsize, hsize);
        return AVERROR_INVALIDDATA;
    }

    switch (ihsize) {
    case  40: // windib
    case  56: // windib v3
    case  64: // OS/2 v2
    case 108: // windib v4
    case 124: // windib v5
        width  = bytestream_get_le32(&buf);
        height = bytestream_get_le32(&buf);
        break;
    case  12: // OS/2 v1
        width  = bytestream_get_le16(&buf);
        height = bytestream_get_le16(&buf);
        break;
    default:
        avpriv_report_missing_feature(avctx, "Information header size %u",
                                      ihsize);
        return AVERROR_PATCHWELCOME;
    }

    /* planes */
    if (bytestream_get_le16(&buf) != 1) {
        av_log(avctx, AV_LOG_ERROR, "invalid BMP header\n");
        return AVERROR_INVALIDDATA;
    }

    depth = bytestream_get_le16(&buf);

    if (ihsize >= 40)
        comp = bytestream_get_le32(&buf);
    else
        comp = BMP_RGB;

    if (comp != BMP_RGB && comp != BMP_BITFIELDS && comp != BMP_RLE4 &&
        comp != BMP_RLE8) {
        av_log(avctx, AV_LOG_ERROR, "BMP coding %d not supported\n", comp);
        return AVERROR_INVALIDDATA;
    }

    if (comp == BMP_BITFIELDS) {
        buf += 20;
        rgb[0] = bytestream_get_le32(&buf);
        rgb[1] = bytestream_get_le32(&buf);
        rgb[2] = bytestream_get_le32(&buf);
        if (ihsize > 40)
        alpha = bytestream_get_le32(&buf);
    }

    ret = ff_set_dimensions(avctx, width, height > 0 ? height : -(unsigned)height);
    if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Failed to set dimensions %d %d\n", width, height);
        return AVERROR_INVALIDDATA;
    }

    avctx->pix_fmt = AV_PIX_FMT_NONE;

    switch (depth) {
    case 32:
        if (comp == BMP_BITFIELDS) {
            if (rgb[0] == 0xFF000000 && rgb[1] == 0x00FF0000 && rgb[2] == 0x0000FF00)
                avctx->pix_fmt = alpha ? AV_PIX_FMT_ABGR : AV_PIX_FMT_0BGR;
            else if (rgb[0] == 0x00FF0000 && rgb[1] == 0x0000FF00 && rgb[2] == 0x000000FF)
                avctx->pix_fmt = alpha ? AV_PIX_FMT_BGRA : AV_PIX_FMT_BGR0;
            else if (rgb[0] == 0x0000FF00 && rgb[1] == 0x00FF0000 && rgb[2] == 0xFF000000)
                avctx->pix_fmt = alpha ? AV_PIX_FMT_ARGB : AV_PIX_FMT_0RGB;
            else if (rgb[0] == 0x000000FF && rgb[1] == 0x0000FF00 && rgb[2] == 0x00FF0000)
                avctx->pix_fmt = alpha ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB0;
            else {
                av_log(avctx, AV_LOG_ERROR, "Unknown bitfields "
                       "%0"PRIX32" %0"PRIX32" %0"PRIX32"\n", rgb[0], rgb[1], rgb[2]);
                return AVERROR(EINVAL);
            }
        } else {
            avctx->pix_fmt = AV_PIX_FMT_BGRA;
        }
        break;
    case 24:
        avctx->pix_fmt = AV_PIX_FMT_BGR24;
        break;
    case 16:
        if (comp == BMP_RGB)
            avctx->pix_fmt = AV_PIX_FMT_RGB555;
        else if (comp == BMP_BITFIELDS) {
            if (rgb[0] == 0xF800 && rgb[1] == 0x07E0 && rgb[2] == 0x001F)
               avctx->pix_fmt = AV_PIX_FMT_RGB565;
            else if (rgb[0] == 0x7C00 && rgb[1] == 0x03E0 && rgb[2] == 0x001F)
               avctx->pix_fmt = AV_PIX_FMT_RGB555;
            else if (rgb[0] == 0x0F00 && rgb[1] == 0x00F0 && rgb[2] == 0x000F)
               avctx->pix_fmt = AV_PIX_FMT_RGB444;
            else {
               av_log(avctx, AV_LOG_ERROR,
                      "Unknown bitfields %0"PRIX32" %0"PRIX32" %0"PRIX32"\n",
                      rgb[0], rgb[1], rgb[2]);
               return AVERROR(EINVAL);
            }
        }
        break;
    case 8:
        if (hsize - ihsize - 14 > 0)
            avctx->pix_fmt = AV_PIX_FMT_PAL8;
        else
            avctx->pix_fmt = AV_PIX_FMT_GRAY8;
        break;
    case 1:
    case 4:
        if (hsize - ihsize - 14 > 0) {
            avctx->pix_fmt = AV_PIX_FMT_PAL8;
        } else {
            av_log(avctx, AV_LOG_ERROR, "Unknown palette for %u-colour BMP\n",
                   1 << depth);
            return AVERROR_INVALIDDATA;
        }
        break;
    default:
        av_log(avctx, AV_LOG_ERROR, "depth %u not supported\n", depth);
        return AVERROR_INVALIDDATA;
    }

    if (avctx->pix_fmt == AV_PIX_FMT_NONE) {
        av_log(avctx, AV_LOG_ERROR, "unsupported pixel format\n");
        return AVERROR_INVALIDDATA;
    }

    if ((ret = ff_get_buffer(avctx, p, 0)) < 0)
        return ret;
    p->pict_type = AV_PICTURE_TYPE_I;
    p->key_frame = 1;

    buf   = buf0 + hsize;
    dsize = buf_size - hsize;

    /* Line size in file multiple of 4 */
    n = ((avctx->width * depth + 31) / 8) & ~3;

    if (n * avctx->height > dsize && comp != BMP_RLE4 && comp != BMP_RLE8) {
        n = (avctx->width * depth + 7) / 8;
        if (n * avctx->height > dsize) {
            av_log(avctx, AV_LOG_ERROR, "not enough data (%d < %d)\n",
                   dsize, n * avctx->height);
            return AVERROR_INVALIDDATA;
        }
        av_log(avctx, AV_LOG_ERROR, "data size too small, assuming missing line alignment\n");
    }

    // RLE may skip decoding some picture areas, so blank picture before decoding
    if (comp == BMP_RLE4 || comp == BMP_RLE8)
        memset(p->data[0], 0, avctx->height * p->linesize[0]);

    if (height > 0) {
        ptr      = p->data[0] + (avctx->height - 1) * p->linesize[0];
        linesize = -p->linesize[0];
    } else {
        ptr      = p->data[0];
        linesize = p->linesize[0];
    }

    if (avctx->pix_fmt == AV_PIX_FMT_PAL8) {
        int colors = 1 << depth;

        memset(p->data[1], 0, 1024);

        if (ihsize >= 36) {
            int t;
            buf = buf0 + 46;
            t   = bytestream_get_le32(&buf);
            if (t < 0 || t > (1 << depth)) {
                av_log(avctx, AV_LOG_ERROR,
                       "Incorrect number of colors - %X for bitdepth %u\n",
                       t, depth);
            } else if (t) {
                colors = t;
            }
        } else {
            colors = FFMIN(256, (hsize-ihsize-14) / 3);
        }
        buf = buf0 + 14 + ihsize; //palette location
        // OS/2 bitmap, 3 bytes per palette entry
        if ((hsize-ihsize-14) < (colors << 2)) {
            if ((hsize-ihsize-14) < colors * 3) {
                av_log(avctx, AV_LOG_ERROR, "palette doesn't fit in packet\n");
                return AVERROR_INVALIDDATA;
            }
            for (i = 0; i < colors; i++)
                ((uint32_t*)p->data[1])[i] = (0xFFU<<24) | bytestream_get_le24(&buf);
        } else {
            for (i = 0; i < colors; i++)
                ((uint32_t*)p->data[1])[i] = 0xFFU << 24 | bytestream_get_le32(&buf);
        }
        buf = buf0 + hsize;
    }
    if (comp == BMP_RLE4 || comp == BMP_RLE8) {
        if (comp == BMP_RLE8 && height < 0) {
            p->data[0]    +=  p->linesize[0] * (avctx->height - 1);
            p->linesize[0] = -p->linesize[0];
        }
        bytestream2_init(&gb, buf, dsize);
        ff_msrle_decode(avctx, p, depth, &gb);
        if (height < 0) {
            p->data[0]    +=  p->linesize[0] * (avctx->height - 1);
            p->linesize[0] = -p->linesize[0];
        }
    } else {
        switch (depth) {
        case 1:
            for (i = 0; i < avctx->height; i++) {
                int j;
                for (j = 0; j < avctx->width >> 3; j++) {
                    ptr[j*8+0] =  buf[j] >> 7;
                    ptr[j*8+1] = (buf[j] >> 6) & 1;
                    ptr[j*8+2] = (buf[j] >> 5) & 1;
                    ptr[j*8+3] = (buf[j] >> 4) & 1;
                    ptr[j*8+4] = (buf[j] >> 3) & 1;
                    ptr[j*8+5] = (buf[j] >> 2) & 1;
                    ptr[j*8+6] = (buf[j] >> 1) & 1;
                    ptr[j*8+7] =  buf[j]       & 1;
                }
                for (j = 0; j < (avctx->width & 7); j++) {
                    ptr[avctx->width - (avctx->width & 7) + j] = buf[avctx->width >> 3] >> (7 - j) & 1;
                }
                buf += n;
                ptr += linesize;
            }
            break;
        case 8:
        case 24:
        case 32:
            for (i = 0; i < avctx->height; i++) {
                memcpy(ptr, buf, n);
                buf += n;
                ptr += linesize;
            }
            break;
        case 4:
            for (i = 0; i < avctx->height; i++) {
                int j;
                for (j = 0; j < n; j++) {
                    ptr[j*2+0] = (buf[j] >> 4) & 0xF;
                    ptr[j*2+1] = buf[j] & 0xF;
                }
                buf += n;
                ptr += linesize;
            }
            break;
        case 16:
            for (i = 0; i < avctx->height; i++) {
                const uint16_t *src = (const uint16_t *) buf;
                uint16_t *dst       = (uint16_t *) ptr;

                for (j = 0; j < avctx->width; j++)
                    *dst++ = av_le2ne16(*src++);

                buf += n;
                ptr += linesize;
            }
            break;
        default:
            av_log(avctx, AV_LOG_ERROR, "BMP decoder is broken\n");
            return AVERROR_INVALIDDATA;
        }
    }
    if (avctx->pix_fmt == AV_PIX_FMT_BGRA) {
        for (i = 0; i < avctx->height; i++) {
            int j;
            uint8_t *ptr = p->data[0] + p->linesize[0]*i + 3;
            for (j = 0; j < avctx->width; j++) {
                if (ptr[4*j])
                    break;
            }
            if (j < avctx->width)
                break;
        }
        if (i == avctx->height)
            avctx->pix_fmt = p->format = AV_PIX_FMT_BGR0;
    }

    *got_frame = 1;

    return buf_size;
}

可以看到其内部通过bytestream_get_byte和bytestream_get_le32等函数读取输入图片的header信息(关于这些bytestream_get_byte函数的内部实现,可以阅读《FFmpeg源码:bytestream_get_byte函数解析》)

比如通过语句 fsize = bytestream_get_le32(&buf) 读取BMP header里面的文件大小。

读取图片的宽度和高度

五、总结

简单的来讲,FFmpeg底层首先通过系统调用open函数打开图片,read函数读取图片数据,然后通过bmp_probe函数判断输入是否为BMP格式图片,如果确定是BMP图片,则会调用函数bmp_decode_frame将图片信息解析出来。

六、参考

《FFmpeg多媒体文件格式探测》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/647212.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

每日一问-如何设置VS Code 中 Markdown粘贴图片的位置

VS Code内的markdown编辑器应该算是比较好用的&#xff0c;但是有一个问题一直困扰着我&#xff0c;就是在编辑markdown文件时&#xff0c;粘贴图片的位置问题。默认情况下&#xff0c;VS Code会将粘贴的图片放在markdown文件的同级目录下&#xff0c;这样会导致markdown文件的…

OWASP top10--SQL注入(二)

目录 06&#xff1a;SQL注入提交方式 6.1、get提交 6.2、post提交 6.3、cookie提交 6.4、HTTP Header头提交 07&#xff1a;注入攻击支持类型 7.1、union注入&#xff1a; 7.1.1、union操作符一般与order by语句配合使用 7.1.2、information_schema注入 7.2、基于函数…

AIGC语音交互

目录 一、总体介绍 二、环境设置与安装 三、语音识别 四、语音合成 五、GPT调用 六、信息关系 七、实现效果 一、总体介绍 达成效果&#xff1a;在ROS系统中实现用户语音提问得到智能语音回答 用到的技术&#xff1a;科大讯飞的语音识别、语音合成 GPT 操作系统&#x…

顶顶通呼叫中心中间件-自动外呼输入分机号(比如隐私号)(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件-自动外呼输入分机号(比如隐私号)(mod_cti基于FreeSWITCH) 比如有些人的号码是这样的就需要用上自动外呼输入分机号了 号码1&#xff1a;182XXXX8111-1234 号码2&#xff1a;182XXXX8222 如果号码是这样的就根据以下步骤配置 注意使用这个需要&#xff1a;…

web如何做接口层面自动化测试?

接口层面约等于集成化测试&#xff0c;且需要启动web容器 一般web项目的&#xff0c;代码都是按照分层开发的&#xff0c;业务主要是集中在service和dao层&#xff0c;而我们如果仅仅是利用之前的单元测试,然后把依赖的代码直接mock掉&#xff0c;仅仅测试controller这一块是没…

如何将前端项目打包并部署到不同服务器环境

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/frontlearningNotes 觉得有帮助的同学&#xff0c;可以点心心支持一下哈&#xff08;笔记是根据b站尚硅谷的前端讲师【张天禹老师】整理的&#xff0c;用于自己复盘&#xff0c;有需要学习的可以去b站学习原版视频&…

【Spring】深入理解 Spring 状态机:简化复杂业务逻辑的利器

前言 在软件开发中&#xff0c;有许多场景需要处理状态转换和状态驱动的逻辑&#xff0c;比如订单处理、工作流程管理、游戏引擎等。Spring 状态机&#xff08;Spring State Machine&#xff09;是 Spring Framework 提供的一个强大的模块&#xff0c;用于帮助开发人员轻松构建…

计算机网络 1

两台主机想通信&#xff0c;其实本质就是两个文件的资源交换&#xff0c;但是长距离的通信&#xff0c;面临的是很多的问题。这个时候需要通过一些方式来保证可靠性 什么是协议 这样一个例子&#xff0c;我是住在农村&#xff0c;我读高中了我需要去县里面读书。这个时候呢&…

01.并发编程简介

1 什么是并发编程 所谓并发编程是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。 2 为什么我们要学习并发编程&#xff1f; 最直白的原因就是因为面试需要&#xff0c;大厂的 Java 岗的并发编程能力属于标配。 而在非大厂…

基于长短期记忆网络 LSTM 的送餐时间预测

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…

Java进阶学习笔记30——BigDecimal

BigDecimal&#xff1a; 用于解决浮点型运算的&#xff0c;出现结果失真的问题。 运行结果&#xff1a; package cn.ensource.d4_bigdecimal;import java.math.BigDecimal;public class Test {public static void main(String[] args) {// 目标&#xff1a;了解BigDecimal类do…

科林Linux5_线程

一、线程基础 进程是操作系统经典的执行任务的生产力。 进程是最小的资源分配单位&#xff0c;进程的内存开销较大&#xff0c;在内存资源不变的情况下&#xff0c;提高进程的执行能力&#xff08;生产力&#xff09; 线程寄存在进程中&#xff0c;与进程共享资源&#xff0…

泛型...

定义&#xff1a;在编译过程中约束操作的数据类型。&#xff08;统一数据类型&#xff09; 格式&#xff1a;<数据类型> 泛型中不能写基本数据类型。 泛型类 在一个类中&#xff0c;某个变量的数据类型不确定时&#xff0c;可以定义带有泛型的类。 泛型的底层是Obje…

Java 泛型基础

目录 1. 为什么使用泛型 2. 泛型的使用方式 2.1. 泛型类 2.2. 泛型接口 2.3. 泛型方法 3. 泛型涉及的符号 3.1. 类型通配符"?" 3.2. 占位符 T/K/V/E 3.3. 占位符T和通配符&#xff1f;的区别。 4. 泛型不变性 5. 泛型编译时擦除 1. 为什么使用泛型 Java 为…

Pandas 模块-操纵数据(12)-处理字符串数据

目录 1. .str 模块 1.1 数据准备 1.2 .str 函数详解 1.2.1 .str capitalize() 首字母大写 1.2.2 .str casefold() 返回字符串的副本 1.2.3 .str cat() 连接输出 1.2.4 .str center(width[,fillchar]) 字符串居中 1.2.5 .str contains() 含有特定字符 1.2.6 .str count() 计…

【代码随想录——回溯算法二周目】

1. 组合总和 var (path []intres [][]int )func combinationSum(candidates []int, target int) [][]int {path make([]int, 0)res make([][]int, 0)dfs(candidates,target,0,0)return res }func dfs(candidates []int, target int,tempTarget int,start int) {if tempTarg…

【Xilinx】常用的全局时钟资源相关Xilinx器件原语

1 概述 常用的与全局时钟资源相关的Xilinx器件原语包括&#xff1a; IBUFGIBUFGDS、OBUFGDS 和 IBUFDS、OBUFDSBUFGBUFGPBUFGCEBUFGMUXBUFGDLLIBUFDS_GTXE1IBUFDS_GTE2IBUFDS_GTE3OBUFDS_GTE3IBUFDS_GTE4OBUFDS_GTE4DCM 刚开始看到这写源语&#xff0c;免不了好奇这些源语对应的…

网络空间安全数学基础·群

重点&#xff1a; 1. 群及子群的定义及相关结论 2. 群的判断,子群的判断 3. 群的阶,元素的阶,它们的相互关系 4. 同态,同构,核子群 2.1群的定义 定义&#xff1a;设G是一非空集合。如果在G上定义了一个代数运算&#xff0c;称为乘法&#xff0c;记为ab&#xff0c;而且这个运…

Ubuntu18.04 OpenSSH升级

升级前版本&#xff1a; rootecs-m2eqyb:/opt# ll total 20912 drwxr-xr-x 2 root root 4096 May 10 16:23 ./ drwxr-xr-x 24 root root 4096 May 10 14:38 ../ -rw-r--r-- 1 root root 1848766 May 10 16:23 openssh-9.7p1.tar.gz -rw-r--r-- 1 root root 18038…

程序包org.springframework.boot不存在

springBoot项目启动报错 程序包org.springframework.boot不存在 1、检查依赖 首先检查pom文件判断依赖是否存在 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.4.5…