【AVD】视频解码时如何获取 coded_width coded_height 即参与编码的宽高

基于字节对齐、宏块宽高等因素,导致一个宽 w 高 h 的视频其实际参与编码的某一帧的宽高并不一定等于 w 和 h,而是有一个 ffmpeg 称之为 coded_width 及 coded_height 的编码宽高。出于另一些原因,例如数据送出与读取、数据加载到纹理等需求,不仅需要知道 w h,还需要知道其 coded_width 及 coded_height(以下简称 cw ch),那么该如何尽可能早地获取到这两个值呢?

解码之前获取不到 cw ch

参考着 FFmpeg 源码中的示例编写的编解码库,使用 avformat_open_input(&format_context, filename, nullptr, nullptr) 打开文件,然后用 avformat_find_stream_info(format_context, nullptr) 查找文件的流信息,再用 int stream_id = av_find_best_stream(format_context, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, false) 获取到视频流的 id,由此即可获取到该文件的视频流信息,其大部分主要信息存储于 format_context->streams[stream_id]->codecpar 这个参数中。包括宽、高、颜色格式、比特率、编码格式、采样宽高比等;而另一些诸如时长、帧数、帧率、时间基等信息也存在于上一层的 format_context->streams[stream_id] 这个 AVStream 结构体变量中。
而以上这些信息里,并没有 coded_width 及 coded_height 这两个信息。通过查 FFmpeg 源码可知,这两个变量是存储于结构体 AVCodecContext 中的,早一些的版本里,AVStream 结构体貌似还有个 AVCodecContext 变量,但 5.0 之后的版本已经去除。那该怎么获取呢?
如上图所示该视频宽为 1080 但参与编码的宽为 1088

如上图所示该视频宽为 1080 但参与编码的宽为 1088,忽然想到,使用命令行 ffprobe -show_streams -show_format 查看文件信息的时候是有这个参数打印的,那么,看一下 ffprobe 的源码不就行了?

查源码

其实全局搜索 coded_width 的时候,出现在比较靠前的搜索结果,或者说第一个存在于 c 文件中的搜索结果就来自 ffprobe.c 这个文件,点过去看,对比其代码上下文及上图 ffprobe 的输出信息,看起来就是打印上图信息的代码位置。
在这里插入图片描述
而这个 dec_ctx 就是个 AVCodecContext *,那它是怎么获取到的呢?找到上图这段代码的函数起始发现,dec_ctx 来自参数 InputStream *ist

static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int in_program)
{
    AVStream *stream = ist->st;
    AVCodecParameters *par;
    AVCodecContext *dec_ctx;
    char val_str[128];
    const char *s;
    AVRational sar, dar;
    AVBPrint pbuf;
    const AVCodecDescriptor *cd;
    int ret = 0;
    const char *profile = NULL;
    
    av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);

    writer_print_section_header(w, in_program ? SECTION_ID_PROGRAM_STREAM : SECTION_ID_STREAM);

    print_int("index", stream->index);

    par     = stream->codecpar;
    dec_ctx = ist->dec_ctx;
	// ...

再找 show_stream 的调用处发现这个 InputStream * ist 来自另一个非 API 标准的自定义结构体 InputFile

static int show_streams(WriterContext *w, InputFile *ifile)
{
    AVFormatContext *fmt_ctx = ifile->fmt_ctx;
    int i, ret = 0;

    writer_print_section_header(w, SECTION_ID_STREAMS);
    for (i = 0; i < ifile->nb_streams; i++)
        if (selected_streams[i]) {
            ret = show_stream(w, fmt_ctx, i, &ifile->streams[i], 0);
            if (ret < 0)
                break;
        }
    writer_print_section_footer(w);

    return ret;
}

再找 show_streams 的调用处,看看这个 InputFile->InputStream->AVCodecContext 是如何获取的即可。

static int probe_file(WriterContext *wctx, const char *filename,
                      const char *print_filename)
{
    InputFile ifile = { 0 };
    int ret, i;
    int section_id;

    do_read_frames = do_show_frames || do_count_frames;
    do_read_packets = do_show_packets || do_count_packets;

    ret = open_input_file(&ifile, filename, print_filename);
	// ...
    if (do_show_streams) {
        ret = show_streams(wctx, &ifile);
        CHECK_END;
    }

是个自定义方法 open_input_file,由于这个方法太长,就不贴代码了。第一遍看到这个方法的时候,感觉没啥特别的,就是它使用 const AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id) 获取到了文件对应的 AVCodec *,再用 avcodec_alloc_context3 申请了一个 AVCodecContext,然后用 avcodec_parameters_to_context(ctx, stream->codecpar)codecpar 中存储的信息拷贝到了 AVCodecContext 中去了。于是,我也比着葫芦画瓢地写到此处,结果发现 ctx->coded_widthctx->coded_height 都是 0,并没有被赋值。
想来也是,结构体 AVCodecParameters 中根本就没有 cw ch 这两个参数,怎么能拷贝到 AVCodecContext 结构体中去呢。于是猜测,是否跟 ffprobe 源码中调用了 avcodec_open2 打开了解码器有关?于是再把代码写到这一句,发现值是有了,但跟正常宽高是一样的,并不是参与编码的宽高。
再细看 open_input_file 这个方法,里面一堆 AVDictionary 相关的操作看得很迷,只能祈祷不是这相关的操作给 cw ch 这两个参数赋值的,虽然迷,仍然艰难地看了下,应该不是。那是哪里呢?
重新看了下 全局搜索 coded_width 的结果,发现大部分语句都是对其值的使用,而对其进行赋值的语句,大约存在于类似 vc1_parser.c vp8_parser.c 等文件里。所以怀疑,cw ch 是各解码器自己的实现中对其进行了赋值,那这,可能就得解码一帧才行吧?
没有头绪,看了半天,又看回最初的 probe_file 这个方法,发现在 open_input_file 之后,还有两个比较可疑的方法调用,一个是avformat_match_stream_specifier,不过这应该是个标准 API,而且参数中也不涉及 InputFile->InputStream->AVCodecContext 这个参数,所以,往后稍稍吧。还有一个就是 read_packets() 这个方法了,这的确是个自定义方法。
找进去发现主要调用了另一个 read_interval_packets 方法,由于这个方法的参数里有 InputFile *,仍然是有可能修改到 InputFile->InputStream->AVCodecContext 这个参数的,因此继续看进去,再分析能修改到那个参数的,就只有一个新的自定义方法 process_frame() 了。看完之后果然是调用了送包和收帧这两大 api,那么也就是说,在解码之前获取不到 cw ch 咯?

照猫画虎

那就仿照这个方法,写吧。写一个读到视频包,然后送进解码器,然后收取解码后的帧的一段程序吧。很快写完之后调试发现
在这里插入图片描述
在代码执行完 avcodec_parameters_to_context() 方法之后,width 和 height 有了 1080 1920 的值,但 cw ch 是 0,在执行完 avcodec_open2 方法之后,cw ch 有值了,但值是 1080 和 1920。继续单步执行下去,仅仅执行完 74 行 avcodec_send_packet 之后,cw ch 的值就变成 1088 1920 了!!!这下,就可以把 75 到 78 行删掉了。最后,不要忘了调用释放 packet 关闭 codec 以及释放 acodec context 的相关代码哟。由于这两张图只是调试用的,并非最终结果,因此并没写释放相关的代码。
在这里插入图片描述
最终代码:

	const AVCodec *codec = avcodec_find_decoder(stream_->codecpar->codec_id);
    AVCodecContext *ctx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(ctx, stream_->codecpar);
    avcodec_open2(ctx, codec, nullptr);
    AVPacket *packet = av_packet_alloc();
    av_init_packet(packet);
    while (!av_read_frame(format_context, packet)) {
      if (packet->stream_index == stream_id_) break; // stream_id_ 是视频的视频流 id
    }
    avcodec_send_packet(ctx, packet);
    av_packet_unref(packet);
    av_packet_free(&packet); // 好像有这一句,上一句 unref 就可以不用写了。不过,写了也没啥开销。
    avcodec_close(ctx);
    avcodec_free_context(&ctx);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
"the emulator process for avd resizable_experimental_api_33 has terminated"这句话意思是“用于可调整大小实验 API 33 的 AVD 的模拟器进程已终止”。 当我们在使用Android Virtual Device(AVD,有会遇到模拟器进程终止的情况。这可能是由于多种原因导致的,包括但不限于以下几种可能: 1. 资源不足:运行模拟器需要大量的计算机资源,如内存和CPU。如果系统资源不足,模拟器进程可能会被操作系统终止。 2. 设备驱动问题:模拟器是通过模拟Android设备的硬件和软件环境来工作的。如果设备驱动有问题,模拟器进程可能会崩溃。 3. 软件冲突:可能与其他正在运行的程序或应用程序冲突,导致模拟器进程崩溃。 当我们遇到这个问题,有几个解决方法可尝试: 1. 重启模拟器:关闭模拟器,并重新启动AVD。 2. 减少模拟器资源使用:如果系统资源不足,我们可以尝试减少模拟器所需的资源。例如,我们可以降低模拟器的分辨率或内存使用量。 3. 更新模拟器和驱动程序:确保我们使用的是最新版本的AVD和相应的设备驱动程序。这可以通过在Android Studio或AVD Manager中更新来实现。 4. 检查软件冲突:确保没有其他应用程序或进程与模拟器冲突。我们可以尝试关闭其他正在运行的应用程序,尤其是占用大量资源的应用程序。 如果以上方法都无效,可能需要进一步检查系统设置、修复驱动程序或重新安装模拟器来解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海Enoch

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值