音视频入门基础:像素格式专题(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复制到AVCodecContextavcodec_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_CLOEXECflags |= O_CLOEXEC;
#endif
#ifdef O_NOINHERITflags |= O_NOINHERIT;
#endiffd = open(filename, flags, mode);
#if HAVE_FCNTLif (fd != -1) {if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)av_log(NULL, AV_LOG_DEBUG, "Failed to set close on exec\n");}
#endifreturn 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;} elsenodat = 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: // windibcase  56: // windib v3case  64: // OS/2 v2case 108: // windib v4case 124: // windib v5width  = bytestream_get_le32(&buf);height = bytestream_get_le32(&buf);break;case  12: // OS/2 v1width  = 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);elsecomp = 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;elseavctx->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 decodingif (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 entryif ((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多媒体文件格式探测》

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

创建一个vue3项目

## 1.创建命令 npm create vuelatest ## 2.具体配置 ## 配置项目名称 √ Project name: vue3_test ## 是否添加TypeScript支持 √ Add TypeScript? Yes ## 是否添加JSX支持 √ Add JSX Support? No ## 是否添加路由环境 √ Add Vue Router for Single Page Application de…

人工智能+量子计算:飞跃现实边界还是科技幻想?

人工智能量子计算&#xff0c;这是一种可能改变世界的伙伴关系。 在科技的前沿&#xff0c;两大革命性技术——人工智能&#xff08;AI&#xff09;和量子计算——正站在合作的十字路口。人工智能&#xff0c;以其强大的数据分析能力和模式识别&#xff0c;正在改变着我们生活…

传感器通过Profinet转Modbus网关与PLC通讯在生产线的应用

Profinet转Modbus&#xff08;XD-MDPN100/300&#xff09;网关可视作一座桥梁&#xff0c;能够实现Profinet协议与Modbus协议相互转换&#xff0c;支持Modbus RTU主站/从站&#xff0c;并且Profinet转Modbus网关设备自带网口和串口&#xff0c;既可以实现协议的转换&#xff0c…

前端基础入门三大核心之HTML篇:探索WebAssembly —— 开启网页高性能应用新时代

前端基础入门三大核心之HTML篇&#xff1a;探索WebAssembly —— 开启网页高性能应用新时代 WebAssembly基础概念工作原理概览WebAssembly实战示例基本使用 安全性与性能优化防范漏洞实践实际工作中的使用技巧结语与讨论 随着Web技术的飞速发展&#xff0c;前端开发者面临越来越…

全文检索ElasticSearch简介

1 全文检索 1.1 什么是全文检索 全文检索是一种通过对文本内容进行全面索引和搜索的技术。它可以快速地在大量文本数据中查找包含特定关键词或短语的文档,并返回相关的搜索结果。全文检索广泛应用于各种信息管理系统和应用中,如搜索引擎、文档管理系统、电子邮件客户端、新闻…

Mac虚拟机工具 CrossOver 24.0.0 Beta3 Mac中文版

CrossOver是一款在Mac上运行Windows应用程序的软件&#xff0c;无需安装虚拟机或重启计算机&#xff0c;简化了操作过程&#xff0c;提高了工作效率&#xff0c;为用户带来便捷体验。前往Mac青桔下载&#xff0c;享受前所未有的便利和高效。摘要由作者通过智能技术生成 CrossOv…

【FAQ】HarmonyOS SDK 闭源开放能力 —IAP Kit(2)

1.问题描述&#xff1a; 应用内支付IAP Kit和Payment Kit的区别以及适用场景&#xff1f; 解决方案&#xff1a; IAP Kit是四方支付&#xff0c;仅支持在线虚拟商品&#xff0c;如会员&#xff0c;游戏钻石等&#xff0c;双框架支持全球&#xff0c;目前单框架暂时只支持国内…

Qml:线程

import QtQuick import QtQuick.Controls Window {width: 640height: 480visible: truetitle: qsTr("Test Thread")//定时器测试//显示时钟Text {id: xtimex:parent.width-220y:parent.height-30text:"time"MouseArea{anchors.fill:parentonClicked:{timer…

【MySQL精通之路】SQL优化(1)-查询优化(8)-嵌套联接优化

主博客&#xff1a; 【MySQL精通之路】SQL优化(1)-查询优化-CSDN博客 上一篇&#xff1a; 【MySQL精通之路】SQL优化(1)-查询优化(7)-嵌套循环联接-CSDN博客 下一篇&#xff1a; 【MySQL精通之路】SQL优化(1)-查询优化(9)-外部联接优化-CSDN博客 与SQL标准相比&#xff0c…

robosuite导入自定义机器人

目录 目的&#xff1a;案例一&#xff1a;成果展示具体步骤&#xff1a;URDF文件准备xml文件生成xml修改机器人构建 目的&#xff1a; 实现其他标准/非标准机器人的构建 案例一&#xff1a; 成果展示 添加机器人JAKA ZU 7 这个模型 具体步骤&#xff1a; URDF文件准备 从…

python-docx 在word中指定位置插入图片或表格

docx库add_picture()方法不支持对图片位置的设置 1、新建一个1行3列的表格&#xff0c;在中间的一列中插入图片 from docx import Document from docx.shared import Pt from docx.oxml.shared import OxmlElement from docx.enum.text import WD_ALIGN_PARAGRAPHdef add_cen…

Nacos 进阶篇---Nacos服务端怎么维护不健康的微服务实例 ?(七)

一、引言 在 Nacos 后台管理服务列表中&#xff0c;我们可以看到微服务列表&#xff0c;其中有一栏叫“健康实例数” &#xff08;如下图&#xff09;&#xff0c;表示对应的客户端实例信息是否可用状态。 那Nacos服务端是怎么感知客户端的状态是否可用呢 &#xff1f; 本章…

Flutter 中的 AnimatedPhysicalModel 小部件:全面指南

Flutter 中的 AnimatedPhysicalModel 小部件&#xff1a;全面指南 Flutter 的 AnimatedPhysicalModel 是一个功能强大的小部件&#xff0c;它允许开发者创建具有物理效果的动画形状变换。这个小部件非常适合需要展示平滑过渡和动态交互的场景&#xff0c;如按钮按下效果、卡片…

Rust:多线程环境下使用 Mutex<T> 还是 Arc<Mutex<T>> ?

在 Rust 中&#xff0c;Mutex 本身不是线程不安全的&#xff1b;它提供了内部的线程同步机制。然而&#xff0c;如果你想在多线程环境中共享同一个 Mutex&#xff0c;你需要确保这个 Mutex 可以被多个线程访问。为此&#xff0c;你通常需要使用 Arc<Mutex<T>>。Arc…

几起 Linux 问题解决分享

&#xff08;首发地址&#xff1a;学习日记 https://www.learndiary.com/2024/05/linux-problems/&#xff09; 朋友们&#xff0c;大家好&#xff01;我是淘宝网学习日记小店专注于Linux服务领域的 learndiary&#xff0c;今天很高兴能与大家分享近期处理的一些Linux故障案例&…

数据库操作入门:PyMongo 和 MongoDB 的基本用法

MongoDB MongoDB是一种流行的NoSQL数据库&#xff0c;它将数据存储在类似JSON的文档中&#xff0c;使数据库非常灵活和可扩展。您可以在https://www.mongodb.com上免费下载MongoDB数据库&#xff0c;或者使用MongoDB云服务&#xff0c;访问https://www.mongodb.com/cloud/atla…

git会忽略我们工作改动中的大小写

在我们日常git工作中&#xff0c;我们对于文件名字的大小写修改正常是不会被git记录的 这是因为默认情况下git是不区分大小写的 这会导致一个问题&#xff0c;由于我们修改了文件名字的大小写&#xff0c;而对于文件之间相互依赖的导入代码没有对应修改 如果我们此时本地推送…

Gone框架介绍25 - Redis模块参考文档

文章目录 Redis 参考文档配置项import 和 bury使用分布是缓存 redis.Cache接口定义使用示例 使用分布式锁 redis.Locker接口定义使用示例 操作Key&#xff0c;使用 redis.Key接口定义 使用 Provider 注入 redis 接口使用示例 直接使用redis连接池接口定义使用示例 Redis 参考文…

基于树的存储数据结构demo

一.简介 由于之前博主尝试Java重构redis&#xff0c;在redis中的的字典数据结构底层也是采用数组实现&#xff0c;字典中存在两个hash表&#xff0c;一个是用于存储数据&#xff0c;另一个被用于rehash扩容为前者两倍。但是我注意到了在redis的数据结构中&#xff0c;并没有像…