【问题标题】:How to convert ffmpeg video frame to YUV444?如何将 ffmpeg 视频帧转换为 YUV444?
【发布时间】:2018-08-12 14:09:56
【问题描述】:

我一直在关注tutorial,了解如何使用ffmpeg 和 SDL 制作一个没有音频的简单视频播放器(目前)。在浏览本教程时,我意识到它已经过时,并且它使用的许多功能,无论是 ffmpeg 还是 SDL,都已被弃用。所以我搜索了一个最新的解决方案,发现了一个stackoverflow问题answer,它完成了教程所缺少的内容。

但是,它使用的是低质量的 YUV420。我想实现 YUV444,在研究了一点色度子采样并查看了 YUV 的不同格式之后,我对如何实现它感到困惑。据我了解,YUV420 的质量是 YUV444 的四分之一。 YUV444 意味着每个像素都有自己的色度样本,因此更详细,而 YUV420 意味着像素被分组在一起并且具有相同的色度样本,因此细节较少。

据我了解,YUV(420、422、444) 的不同格式在它们对 y、u 和 v 的排序方式上是不同的。所有这一切都让人有点不知所措,因为我没有对编解码器做太多事情,转换等。任何帮助将不胜感激,如果需要更多信息,请在投票前告诉我。

这是我提到的answer关于转换为YUV420的代码:

texture = SDL_CreateTexture(
        renderer,
        SDL_PIXELFORMAT_YV12,
        SDL_TEXTUREACCESS_STREAMING,
        pCodecCtx->width,
        pCodecCtx->height
        );
    if (!texture) {
        fprintf(stderr, "SDL: could not create texture - exiting\n");
        exit(1);
    }

    // initialize SWS context for software scaling
    sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
        pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
        AV_PIX_FMT_YUV420P,
        SWS_BILINEAR,
        NULL,
        NULL,
        NULL);

    // set up YV12 pixel array (12 bits per pixel)
    yPlaneSz = pCodecCtx->width * pCodecCtx->height;
    uvPlaneSz = pCodecCtx->width * pCodecCtx->height / 4;
    yPlane = (Uint8*)malloc(yPlaneSz);
    uPlane = (Uint8*)malloc(uvPlaneSz);
    vPlane = (Uint8*)malloc(uvPlaneSz);
    if (!yPlane || !uPlane || !vPlane) {
        fprintf(stderr, "Could not allocate pixel buffers - exiting\n");
        exit(1);
    }

    uvPitch = pCodecCtx->width / 2;
    while (av_read_frame(pFormatCtx, &packet) >= 0) {
        // Is this a packet from the video stream?
        if (packet.stream_index == videoStream) {
            // Decode video frame
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

            // Did we get a video frame?
            if (frameFinished) {
                AVPicture pict;
                pict.data[0] = yPlane;
                pict.data[1] = uPlane;
                pict.data[2] = vPlane;
                pict.linesize[0] = pCodecCtx->width;
                pict.linesize[1] = uvPitch;
                pict.linesize[2] = uvPitch;

                // Convert the image into YUV format that SDL uses
                sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
                    pFrame->linesize, 0, pCodecCtx->height, pict.data,
                    pict.linesize);

                SDL_UpdateYUVTexture(
                    texture,
                    NULL,
                    yPlane,
                    pCodecCtx->width,
                    uPlane,
                    uvPitch,
                    vPlane,
                    uvPitch
                    );

                SDL_RenderClear(renderer);
                SDL_RenderCopy(renderer, texture, NULL, NULL);
                SDL_RenderPresent(renderer);

            }
        }

        // Free the packet that was allocated by av_read_frame
        av_free_packet(&packet);
        SDL_PollEvent(&event);
        switch (event.type) {
            case SDL_QUIT:
                SDL_DestroyTexture(texture);
                SDL_DestroyRenderer(renderer);
                SDL_DestroyWindow(screen);
                SDL_Quit();
                exit(0);
                break;
            default:
                break;
        }

    }

    // Free the YUV frame
    av_frame_free(&pFrame);
    free(yPlane);
    free(uPlane);
    free(vPlane);

    // Close the codec
    avcodec_close(pCodecCtx);
    avcodec_close(pCodecCtxOrig);

    // Close the video file
    avformat_close_input(&pFormatCtx);

编辑:

经过更多研究,我了解到在 YUV420 中首先存储所有 Y,然后依次存储 U 和 V 字节的组合,如下图所示:
(来源:wikimedia.org

但是我也了解到 YUV444 是按照 U、Y、V 的顺序存储的,并且像这张图所示的那样重复:

我尝试在代码中改变一些东西:

    // I changed SDL_PIXELFORMAT_YV12 to SDL_PIXELFORMAT_UYVY
    // as to reflect the order of YUV444
    texture = SDL_CreateTexture(
        renderer,
        SDL_PIXELFORMAT_UYVY,
        SDL_TEXTUREACCESS_STREAMING,
        pCodecCtx->width,
        pCodecCtx->height
        );
    if (!texture) {
        fprintf(stderr, "SDL: could not create texture - exiting\n");
        exit(1);
    }

    // Changed AV_PIX_FMT_YUV420P to AV_PIX_FMT_YUV444P
    // for rather obvious reasons
    sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
        pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
        AV_PIX_FMT_YUV444P,
        SWS_BILINEAR,
        NULL,
        NULL,
        NULL);

    // There are as many Y, U and V bytes as pixels I just
    // made yPlaneSz and uvPlaneSz equal to the number of pixels
    yPlaneSz = pCodecCtx->width * pCodecCtx->height;
    uvPlaneSz = pCodecCtx->width * pCodecCtx->height;
    yPlane = (Uint8*)malloc(yPlaneSz);
    uPlane = (Uint8*)malloc(uvPlaneSz);
    vPlane = (Uint8*)malloc(uvPlaneSz);
    if (!yPlane || !uPlane || !vPlane) {
        fprintf(stderr, "Could not allocate pixel buffers - exiting\n");
        exit(1);
    }

    uvPitch = pCodecCtx->width * 2;
    while (av_read_frame(pFormatCtx, &packet) >= 0) {
        // Is this a packet from the video stream?
        if (packet.stream_index == videoStream) {
            // Decode video frame
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

            // Rearranged the order of the planes to reflect UYV order
            // then set linesize to the number of Y, U and V bytes
            // per row
            if (frameFinished) {
                AVPicture pict;
                pict.data[0] = uPlane;
                pict.data[1] = yPlane;
                pict.data[2] = vPlane;
                pict.linesize[0] = pCodecCtx->width;
                pict.linesize[1] = pCodecCtx->width;
                pict.linesize[2] = pCodecCtx->width;

                // Convert the image into YUV format that SDL uses
                sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
                    pFrame->linesize, 0, pCodecCtx->height, pict.data,
                    pict.linesize);

                SDL_UpdateYUVTexture(
                    texture,
                    NULL,
                    yPlane,
                    1,
                    uPlane,
                    uvPitch,
                    vPlane,
                    uvPitch
                    );
//.................................................

但现在我在拨打SDL_UpdateYUVTexture 时遇到了访问冲突...老实说,我不确定出了什么问题。我认为这可能与不正确地设置AVPicture pic 的成员datalinesize 有关,但我并不积极。

【问题讨论】:

  • 大部分例子使用YUV420p的原因是因为大部分视频内部存储在YUV420p中。典型的显示应用程序应该按原样显示视频,而不是在显示之前将其转换为其他内容...
  • @RonaldS.Bultje 我明白,你不能从半加仑的罐子里得到一加仑的牛奶。也就是说你不能从更少的信息(色度)中得到更多的细节。但是,如果有更高的分辨率,我希望能够利用它,这就是我要问的原因。我不想假设所有视频都使用 YUV420 并失去质量。我打算让它播放更高分辨率的视频。

标签: c++ c ffmpeg yuv subsampling


【解决方案1】:

经过数小时在网上搜索可能的答案后,我偶然发现了这个post,其中有人询问 YUV444 是否支持打包或平面模式。我发现的唯一当前格式是打包的 AYUV。

他们得到的答案是所有当前支持的格式列表,其中不包括 AYUV。因此 SDL 不支持 YUV444。

唯一的解决方案是使用支持 AYUV / YUV444 的不同库。

【讨论】:

  • 从试图弄清​​楚这一点中学到了很多东西。很高兴我做到了,即使我无法找到我正在寻找的答案。总是乐于学习。 :)
  • 技术上是的,YUV420 的质量可能是 YUV444 的四分之一,但实际上对于普通用户来说差异并不明显,而前者的收益相当可观。
  • @thekamilz 我很快就知道了这一点。播放更多视频文件后,我发现 YUV420 无处不在,但尝试将其转换为更高质量却行不通。我尝试使用 YUY2 格式将其转换为 YUV422。这是不成功的。然后我将其转换为 RGB24,它看起来几乎相同并且速度较慢。所以我学会了接受YUV420。 :)
  • 好的,请记住这一点,在专业的广播领域,用于在广播中心之间传输视频(称为贡献)的 yuv422 格式几乎总是使用 yuv420 作为标准(通常8 位用于 SD/HD 或 10 位用于 UHD 内容)。众所周知的编解码器 mpeg2、h264 avc、h265 hevc 也需要这些格式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-18
  • 1970-01-01
  • 2017-04-20
  • 2012-01-16
  • 2018-08-20
  • 2017-02-26
相关资源
最近更新 更多