libav: Store h264 frames in mp4 container

64 Views Asked by At

I'm making a C++ application that retrieves frames from a camera and then encodes each frame with a H264 encoder (not using libav). This encoded H264 frame is then kept in memory as a void *mem as I need to do several things with the encoded frame.

One of the things I need to do, is store the frames (so the void *mem pointers) in a .mp4 container using libavcodec/libavformat. I do NOT want to transcode each frame, I just want to store them directly into the mp4 container.

Preferably for each individual frame that I push through, I get the resulting data as a return type from the function (not sure if this is possible?). If this is not possible, then writing to a file directly is OK as well.

How does one go about doing this with libav?

The only part I have got so far, and where I'm getting stuck, is this:

/*
some private fields accessible in MP4Muxer:
int frameWidth_, frameHeight_, frameRate_, srcBitRate_;
*/


void MP4Muxer::muxFrame(void *mem, size_t len, int64_t timestamp, bool keyFrame) {
    const AVOutputFormat* outputFormat = av_guess_format("mp4", NULL, NULL);
    AVFormatContext* outputFormatContext = avformat_alloc_context();
    outputFormatContext->oformat = outputFormat;
    AVStream* videoStream = avformat_new_stream(outputFormatContext, NULL);

    videoStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    videoStream->codecpar->codec_id = AV_CODEC_ID_H264;
    videoStream->codecpar->width = frameWidth_;
    videoStream->codecpar->height = frameHeight_;
    videoStream->avg_frame_rate = (AVRational) {frameRate_, 1};
    videoStream->time_base = (AVRational) {1, 90000};

}

How do I continue from here? Are there any good resources I can follow? There are some resources I found online, but all of them either write the output directly to a file, read input directly from streams/files etc. so I have a hard time translating them to my needs.

1

There are 1 best solutions below

0
ImJustACowLol On

After a ton of reading, reverse engineering FFMPEG etc., I came up with the following solution. I'm not sure if the code is 100% correct as it is based on a lot of assumptions.

MP4Muxer.hpp:

#include <stdint.h>
#include <stddef.h>

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

class MP4Muxer {
public:
    MP4Muxer(int frameWidth, int frameHeight, int frameRate, int bitRate);
    void initNewStream();
    void remuxFrame(void *mem, size_t len, uint64_t timestamp, bool keyFrame);
    void flush();
private:
    int frameWidth_;
    int frameHeight_;
    int frameRate_;
    int bitRate_;

    uint64_t startTimeStamp_ = 0;

    AVStream* videoStream;
    AVFormatContext* outputFormatContext;
    AVIOContext* ioContext;
    AVCodec* codec;
    AVPacket packet;


};

MP4Muxer.cpp:

#include "MP4Muxer.hpp"
#include <iostream>
#include <string>

MP4Muxer::MP4Muxer(int frameWidth, int frameHeight, int frameRate, int bitRate) : frameWidth_(frameWidth),  frameHeight_(frameHeight), frameRate_(frameRate), bitRate_(bitRate) {
    initNewStream();
}

void MP4Muxer::initNewStream() {
    av_log_set_level(AV_LOG_QUIET);

    const AVOutputFormat* outputFormat = av_guess_format("mp4", "/home/pi/Desktop/test.mp4", NULL);
    if (!outputFormat) {
        std::cerr << "Failed to determine output format" << std::endl;
    }
    //outputFormat->video_codec = AV_CODEC_ID_H264;
    avformat_alloc_output_context2(&outputFormatContext, outputFormat, "mp4", "/home/pi/Desktop/test.mp4");
    if (!outputFormatContext) {
        std::cerr << "Failed to create output context" << std::endl;
    }
    outputFormatContext->oformat = outputFormat;

    videoStream = avformat_new_stream(outputFormatContext, NULL);
    if (!videoStream) {
        std::cerr << "Failed to create dat stream boii" << std::endl;
    }
    videoStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    videoStream->codecpar->codec_id = AV_CODEC_ID_H264;
    videoStream->codecpar->width = frameWidth_;
    videoStream->codecpar->height = frameHeight_;
    videoStream->codecpar->bit_rate = bitRate_;
    videoStream->codecpar->format = AV_PIX_FMT_YUV420P;
    videoStream->avg_frame_rate = (AVRational) {frameRate_, 1};
    videoStream->time_base.num = 1;
    videoStream->time_base.den = 90000;
    videoStream->codecpar->codec_tag = 0;

    av_dump_format(outputFormatContext, 0, "/home/pi/Desktop/test.mp4", 1);

    if (avio_open(&ioContext, "/home/pi/Desktop/test.mp4", AVIO_FLAG_WRITE) < 0)  {
        std::cerr << "failed to create mp4 or something? avio open, anyways..." << std::endl;
    }
    if (!ioContext) {
        std::cerr << "Failed to create ioContext" << std::endl;
    }
    outputFormatContext->pb = ioContext;

    AVDictionary* options = NULL;
    av_dict_set(&options, "framerate", std::to_string(frameRate_).c_str(), 0);
    if (avformat_write_header(outputFormatContext, &options) < 0) {
        std::cerr << "Failed to write headers" << std::endl;
    }

    packet = {};
}

void MP4Muxer::remuxFrame(void *mem, size_t len, uint64_t timestamp, bool keyFrame) {
    if (startTimeStamp_ == 0) {
        startTimeStamp_ = timestamp;
    }
    AVPacket packet = {};
    av_init_packet(&packet);
    packet.stream_index = videoStream->index;
    packet.flags = 0;
    packet.pts = packet.dts = uint64_t(double(90000) * ((double(timestamp) - double(startTimeStamp_)) / double(1000000)));
    packet.data = static_cast<uint8_t*>(mem);
    packet.size = len;
    av_packet_rescale_ts(&packet, videoStream->time_base, videoStream->time_base);
    packet.pos = -1;
    int writeResult = av_interleaved_write_frame(outputFormatContext, &packet);
    if (writeResult < 0) {
        std::cerr << "Failed to write frame: " << writeResult << std::endl;
    }
}

void MP4Muxer::flush() {
    av_write_trailer(outputFormatContext);

    avio_closep(&outputFormatContext->pb);
    avformat_free_context(outputFormatContext);
    startTimeStamp_ = 0;
}