首页 > 解决方案 > 使用 FFmpeg API 创建的 MP4 无法在媒体播放器中播放

问题描述

这几天我一直在为这个问题苦苦挣扎。这里和网络上都发布了类似的问题,但似乎没有一个解决方案对我有用。他们可能已经过时了?

这是我用来生成 MP4 文件的当前代码迭代。

它会生成一个简单的 2 秒 .mp4 文件,无法在我尝试过的任何播放器中播放。如果我通过 FFmpeg 命令行运行该 mp4 文件,它将从中生成完美可播放的电影。所以数据就在那里。

此外,如果您将此代码中的输出文件名从 .mp4 修改为 .avi,则此代码也会生成一个可播放的 avi 文件。所以无论它是什么,它都与 H.264 格式相关联。

我确定我错过了一些简单的东西,但对于我的生活,我无法弄清楚那是什么。

任何帮助将不胜感激!

这是 VC++ 项目的链接。电影制作者.zip

电影制作者.h

#pragma once

extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
}

class FMovieMaker
{
public:
    ~FMovieMaker();

    bool Initialize(const char* FileName, int Width = 1920, int Height = 1080, int FPS = 30, int BitRate = 2000);
    bool RecordFrame(uint8_t* BGRAData);
    bool Finalize();

    bool IsInitialized() const { return bInitialized; }
    int GetWidth() const { return CodecContext ? CodecContext->width : 0; }
    int GetHeight() const { return CodecContext ? CodecContext->height : 0; }

private:
    bool EncodeFrame(bool bFinalize);
    void Log(const char* fmt, ...);

    AVOutputFormat* OutputFormat = nullptr;
    AVFormatContext* FormatContext = nullptr;
    AVCodecContext* CodecContext = nullptr;
    AVFrame* Frame = nullptr;
    SwsContext* ColorConverter = nullptr;
    int64_t RecordedFrames = 0;
    bool bInitialized = false;
};

电影制作者.cpp

#include "MovieMaker.h"

FMovieMaker::~FMovieMaker()
{
    if (IsInitialized())
        Finalize();
}

bool FMovieMaker::Initialize(const char* FileName, int Width /*= 1920*/, int Height /*= 1080*/, int FPS /*= 30*/, int BitRate /*= 2000*/)
{
    OutputFormat = av_guess_format(nullptr, FileName, nullptr);
    if (!OutputFormat)
    {
        Log("Couldn't guess the output format from the filename: %s", FileName);
        return false;
    }

    AVCodecID CodecID = OutputFormat->video_codec;
    if (CodecID == AV_CODEC_ID_NONE)
    {
        Log("Could not determine a codec to use");
        return false;
    }

    /* allocate the output media context */
    int ErrorCode = avformat_alloc_output_context2(&FormatContext, OutputFormat, nullptr, FileName);
    if (ErrorCode < 0)
    {
        char Error[AV_ERROR_MAX_STRING_SIZE];
        av_make_error_string(Error, AV_ERROR_MAX_STRING_SIZE, ErrorCode);
        Log("Failed to allocate format context: %s", Error);
        return false;
    }
    else if (!FormatContext)
    {
        Log("Failed to get format from filename: %s", FileName);
        return false;
    }

    /* find the video encoder */
    const AVCodec* Codec = avcodec_find_encoder(CodecID);
    if (!Codec)
    {
        Log("Codec '%d' not found", CodecID);
        return false;
    }

    /* create the video stream */
    AVStream* Stream = avformat_new_stream(FormatContext, Codec);
    if (!Stream)
    {
        Log("Failed to allocate stream");
        return false;
    }

    /* create the codec context */
    CodecContext = avcodec_alloc_context3(Codec);
    if (!CodecContext)
    {
        Log("Could not allocate video codec context");
        return false;
    }

    Stream->codecpar->codec_id = OutputFormat->video_codec;
    Stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    Stream->codecpar->width = Width;
    Stream->codecpar->height = Height;
    Stream->codecpar->format = AV_PIX_FMT_YUV420P;
    Stream->codecpar->bit_rate = (int64_t)BitRate * 1000;
    avcodec_parameters_to_context(CodecContext, Stream->codecpar);

    CodecContext->time_base = { 1, FPS };
    CodecContext->max_b_frames = 2;
    CodecContext->gop_size = 12;
    CodecContext->framerate = { FPS, 1 };

    if (Stream->codecpar->codec_id == AV_CODEC_ID_H264)
        av_opt_set(CodecContext, "preset", "medium", 0);
    else if (Stream->codecpar->codec_id == AV_CODEC_ID_H265)
        av_opt_set(CodecContext, "preset", "medium", 0);

    avcodec_parameters_from_context(Stream->codecpar, CodecContext);

    if (FormatContext->oformat->flags & AVFMT_GLOBALHEADER)
        CodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    if ((ErrorCode = avcodec_open2(CodecContext, Codec, NULL)) < 0)
    {
        char Error[AV_ERROR_MAX_STRING_SIZE];
        av_make_error_string(Error, AV_ERROR_MAX_STRING_SIZE, ErrorCode);
        Log("Failed to open codec: %s", Error);
        return false;
    }

    if (!(OutputFormat->flags & AVFMT_NOFILE))
    {
        if ((ErrorCode = avio_open(&FormatContext->pb, FileName, AVIO_FLAG_WRITE)) < 0)
        {
            char Error[AV_ERROR_MAX_STRING_SIZE];
            av_make_error_string(Error, AV_ERROR_MAX_STRING_SIZE, ErrorCode);
            Log("Failed to open file: %s", Error);
            return false;
        }
    }

    Stream->time_base = CodecContext->time_base;
    if ((ErrorCode = avformat_write_header(FormatContext, NULL)) < 0)
    {
        char Error[AV_ERROR_MAX_STRING_SIZE];
        av_make_error_string(Error, AV_ERROR_MAX_STRING_SIZE, ErrorCode);
        Log("Failed to write header: %s", Error);
        return false;
    }

    CodecContext->time_base = Stream->time_base;

    av_dump_format(FormatContext, 0, FileName, 1);

    // create the frame
    {
        Frame = av_frame_alloc();
        if (!Frame)
        {
            Log("Could not allocate video frame");
            return false;
        }
        Frame->format = CodecContext->pix_fmt;
        Frame->width = CodecContext->width;
        Frame->height = CodecContext->height;

        ErrorCode = av_frame_get_buffer(Frame, 32);
        if (ErrorCode < 0)
        {
            char Error[AV_ERROR_MAX_STRING_SIZE];
            av_make_error_string(Error, AV_ERROR_MAX_STRING_SIZE, ErrorCode);
            Log("Could not allocate the video frame data: %s", Error);
            return false;
        }
    }

    // create a color converter
    {
        ColorConverter = sws_getContext(CodecContext->width, CodecContext->height, AV_PIX_FMT_BGRA,
                                        CodecContext->width, CodecContext->height, AV_PIX_FMT_YUV420P, 0, 0, 0, 0);
        if (!ColorConverter)
        {
            Log("Could not allocate color converter");
            return false;
        }
    }

    bInitialized = true;
    return true;
}

bool FMovieMaker::RecordFrame(uint8_t* BGRAData)
{
    if (!bInitialized)
    {
        Log("Cannot record frames on an uninitialized Video Recorder");
        return false;
    }

    /*make sure the frame data is writable */
    int ErrorCode = av_frame_make_writable(Frame);
    if (ErrorCode < 0)
    {
        char Error[AV_ERROR_MAX_STRING_SIZE];
        av_make_error_string(Error, AV_ERROR_MAX_STRING_SIZE, ErrorCode);
        Log("Could not make the frame writable: %s", Error);
        return false;
    }

    /* convert the bgra bitmap data into yuv frame data */
    int inLinesize[1] = { 4 * CodecContext->width }; // RGB stride
    sws_scale(ColorConverter, &BGRAData, inLinesize, 0, CodecContext->height, Frame->data, Frame->linesize);

    //Frame->pts = RecordedFrames++;
    Frame->pts = CodecContext->time_base.den / CodecContext->time_base.num * CodecContext->framerate.den / CodecContext->framerate.num * (RecordedFrames++);
    //The following assumes that codecContext->time_base = (AVRational){1, 1};
    //Frame->pts = frameduration * (RecordedFrames++) * Stream->time_base.den / (Stream->time_base.num * fps);
    //Frame->pts += av_rescale_q(1, CodecContext->time_base, Stream->time_base);

    return EncodeFrame(false);
}

bool FMovieMaker::EncodeFrame(bool bFinalize)
{
    /* send the frame to the encoder */
    int ErrorCode = avcodec_send_frame(CodecContext, bFinalize ? nullptr : Frame);
    if (ErrorCode < 0)
    {
        char Error[AV_ERROR_MAX_STRING_SIZE];
        av_make_error_string(Error, AV_ERROR_MAX_STRING_SIZE, ErrorCode);
        Log("Error sending a frame for encoding: %s", Error);
        return false;
    }

    AVPacket Packet;
    av_init_packet(&Packet);
    Packet.data = NULL;
    Packet.size = 0;
    Packet.flags |= AV_PKT_FLAG_KEY;
    Packet.pts = Frame->pts;

    if (avcodec_receive_packet(CodecContext, &Packet) == 0)
    {
        //std::cout << "pkt key: " << (Packet.flags & AV_PKT_FLAG_KEY) << " " << Packet.size << " " << (counter++) << std::endl;
        uint8_t* size = ((uint8_t*)Packet.data);
        //std::cout << "first: " << (int)size[0] << " " << (int)size[1] << " " << (int)size[2] << " " << (int)size[3] << " " << (int)size[4] << " " << (int)size[5] << " " << (int)size[6] << " " << (int)size[7] << std::endl;

        av_interleaved_write_frame(FormatContext, &Packet);
        av_packet_unref(&Packet);
    }

    return true;
}

bool FMovieMaker::Finalize()
{
    if (!bInitialized)
    {
        Log("Cannot finalize uninitialized Video Recorder");
        return false;
    }

    //DELAYED FRAMES
    AVPacket Packet;
    av_init_packet(&Packet);
    Packet.data = NULL;
    Packet.size = 0;

    for (;;)
    {
        avcodec_send_frame(CodecContext, NULL);
        if (avcodec_receive_packet(CodecContext, &Packet) == 0)
        {
            av_interleaved_write_frame(FormatContext, &Packet);
            av_packet_unref(&Packet);
        }
        else
            break;
    }

    av_write_trailer(FormatContext);
    if (!(OutputFormat->flags & AVFMT_NOFILE))
    {
        int ErrorCode = avio_close(FormatContext->pb);
        if (ErrorCode < 0)
        {
            char Error[AV_ERROR_MAX_STRING_SIZE];
            av_make_error_string(Error, AV_ERROR_MAX_STRING_SIZE, ErrorCode);
            Log("Failed to close file: %s", Error);
        }
    }

    if (Frame)
    {
        av_frame_free(&Frame);
        Frame = nullptr;
    }

    if (CodecContext)
    {
        avcodec_free_context(&CodecContext);
        CodecContext = nullptr;
    }

    if (FormatContext)
    {
        avformat_free_context(FormatContext);
        FormatContext = nullptr;
    }

    if (ColorConverter)
    {
        sws_freeContext(ColorConverter);
        ColorConverter = nullptr;
    }

    bInitialized = false;
    return true;
}

void FMovieMaker::Log(const char* fmt, ...)
{
    va_list args;
    fprintf(stderr, "LOG: ");
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    fprintf(stderr, "\n");
}

主文件

#include "MovieMaker.h"

uint8_t FtoB(float x)
{
    if (x <= 0.0f)
        return 0;
    if (x >= 1.0f)
        return 255;
    else
        return (uint8_t)(x * 255.0f);
}

void SetPixelColor(float X, float Y, float Width, float Height, float t, uint8_t* BGRA)
{
    t += 12.0f; // more interesting colors at this time

    float P[2] = { 0.1f * X - 25.0f, 0.1f * Y - 25.0f };
    float V = sqrtf(P[0] * P[0] + P[1] * P[1]);
    BGRA[0] = FtoB(sinf(V + t / 0.78f));
    BGRA[1] = FtoB(sinf(V + t / 10.0f));
    BGRA[2] = FtoB(sinf(V + t / 36e2f));
    BGRA[3] = 255;
}

int main()
{
    FMovieMaker MovieMaker;

    const char* FileName = "C:\\ffmpeg\\MyMovieMakerMovie.mp4";
    int Width = 640;
    int Height = 480;
    int FPS = 30;
    int BitRateKBS = 2000;

    if (MovieMaker.Initialize(FileName, Width, Height, FPS, BitRateKBS))
    {
        int Size = Width * 4 * Height;
        uint8_t* BGRAData = new uint8_t[Size];
        memset(BGRAData, 255, Size);

        for (float Frame = 0; Frame < 60; Frame++)
        {
            // fill the image data with something interesting
            for (float Y = 0; Y < Height; Y++)
            {
                for (float X = 0; X < Width; X++)
                {
                    SetPixelColor(X, Y, (float)Width, (float)Height, Frame / (float)FPS, &BGRAData[(int)(Y * Width + X) * 4]);
                }
            }

            if (!MovieMaker.RecordFrame(BGRAData))
                break;
        }

        delete[] BGRAData;

        MovieMaker.Finalize();
    }
}

如果我有添加AV_CODEC_FLAG_GLOBAL_HEADER如上所示标志的行,我会在输出中遇到各种问题ffprobe MyMovieMakerMovie.mp4

C:\ffmpeg>ffprobe MyMovieMakerMovie.mp4
ffprobe version 4.2.2 Copyright (c) 2007-2019 the FFmpeg developers
  built with gcc 9.2.1 (GCC) 20200122
  configuration: --disable-static --enable-shared --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
[h264 @ 000001d44b795b00] non-existing PPS 0 referenced
[h264 @ 000001d44b795b00] decode_slice_header error
[h264 @ 000001d44b795b00] no frame!
...
[h264 @ 000001d44b795b00] non-existing PPS 0 referenced
[h264 @ 000001d44b795b00] decode_slice_header error
[h264 @ 000001d44b795b00] no frame!
[mov,mp4,m4a,3gp,3g2,mj2 @ 000001d44b783880] decoding for stream 0 failed
[mov,mp4,m4a,3gp,3g2,mj2 @ 000001d44b783880] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none, 640x480, 20528 kb/s): unspecified pixel format
Consider increasing the value for the 'analyzeduration' and 'probesize' options
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'MyMovieMakerMovie.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.29.100
  Duration: 00:00:01.97, start: 0.000000, bitrate: 20529 kb/s
    Stream #0:0(und): Video: h264 (avc1 / 0x31637661), none, 640x480, 20528 kb/s, 30.51 fps, 30 tbr, 15360 tbn, 30720 tbc (default)
    Metadata:
      handler_name    : VideoHandler

在不添加AV_CODEC_FLAG_GLOBAL_HEADER标志的情况下,我从 ffprobe 获得了干净的输出,但视频仍然无法播放。注意它认为帧速率是 30.51,我不知道为什么。

C:\ffmpeg>ffprobe MyMovieMakerMovie.mp4
ffprobe version 4.2.2 Copyright (c) 2007-2019 the FFmpeg developers
  built with gcc 9.2.1 (GCC) 20200122
  configuration: --disable-static --enable-shared --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'MyMovieMakerMovie.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.29.100
  Duration: 00:00:01.97, start: 0.000000, bitrate: 20530 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x480, 20528 kb/s, 30.51 fps, 30 tbr, 15360 tbn, 60 tbc (default)
    Metadata:
      handler_name    : VideoHandler

标签: c++videoffmpeg

解决方案


推荐阅读