mercredi 25 février 2015

FFMPEG h.264 encoding / MPEG2 ts streaming problems

Class prototype is as follows:



#ifndef _FULL_MOTION_VIDEO_STREAM_H_
#define _FULL_MOTION_VIDEO_STREAM_H_

#include <memory>
#include <string>

#ifndef INT64_C
# define INT64_C(c) (c ## LL)
# define UINT64_C(c) (c ## ULL)
#endif

extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/imgutils.h"
#include "libavutil/mathematics.h"
#include "libavutil/samplefmt.h"
#include "libavformat/avformat.h"

#include <libavutil/timestamp.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}

class FMVStream
{
public:

///
/// Constructor
///
FMVStream();

///
/// Destructor
///
~FMVStream();

///
/// Frame encoder helper function
///
/// Encodes a raw RGB frame into the transport stream
///
void EncodeFrame(const uint8_t* frame);

///
/// Frame width setter
///
void setFrameWidth(const int width);

///
/// Frame width getter
///
int getFrameWidth() const;

///
/// Frame height setter
///
void setFrameHeight(const int height);

///
/// Frame height getter
///
int getFrameHeight() const;

///
/// Stream address setter
///
void setStreamAddress(const std::string& address);

///
/// Stream address getter
///
std::string getStreamAddress() const;

private:

///
/// Video Stream creation
///
AVStream* CreateVideoStream(AVFormatContext* oc);

///
/// Raw frame transcoder
///
/// This will convert the raw RGB frame to a raw YUV frame necessary for h.264 encoding
///
void CopyFrameData(const uint8_t* src_frame);

///
/// Video frame allocator
///
void AllocPicture(PixelFormat pix_fmt, int width, int height);

///
/// Debug print helper function
///
void print_sdp(AVFormatContext **avc, int n);

// formatting data needed for output streaming and the output container (MPEG 2 TS)
AVOutputFormat* format;
AVFormatContext* format_ctx;
AVStream* stream;

// formatting data for frame (Input -> RGB 24 / Output encoded YUV)
AVFrame* pic;
AVIOContext* io_ctx;

std::string streamFilename;

uint8_t* outbuf;
uint32_t outbuf_size;
uint32_t frame_count;

int frameWidth;
int frameHeight;

};

#endif


This block starts the class declaration.



#include "FullMotionVideoStream.h"

#include <stdexcept>
#include <iostream>

FMVStream::FMVStream()
: format(0),
format_ctx(0),
stream(0),
pic(0),
io_ctx(0),
streamFilename("test.mpeg"),
outbuf(0),
outbuf_size(0),
frame_count(0),
frameWidth(640),
frameHeight(480)
{
// Register all formats and codecs
av_register_all();
avcodec_register_all();

// Init networking
avformat_network_init();

// Find format
this->format = av_guess_format("mpegts", NULL, NULL);

// allocate the AVFormatContext
this->format_ctx = avformat_alloc_context();

if (!this->format_ctx)
{
throw std::runtime_error("avformat_alloc_context failed");
}

this->format_ctx->oformat = this->format;
sprintf_s(this->format_ctx->filename, sizeof(this->format_ctx->filename), "%s", this->streamFilename.c_str());

this->stream = CreateVideoStream(this->format_ctx);

// Allocate encoder buffer
this->outbuf_size = 4096 * 400; // Must be large enough to contain one encoded frame
this->outbuf = new uint8_t[this->outbuf_size];

// Allocate AVIOContext
int ret = avio_open(&this->io_ctx, this->streamFilename.c_str(), AVIO_FLAG_WRITE);

if (ret != 0)
{
throw std::runtime_error("avio_open failed");
}

this->format_ctx->pb = this->io_ctx;

// Allocate a frame
AllocPicture(PIX_FMT_YUV420P, this->frameWidth, this->frameHeight);

// Print some debug info about the format
av_dump_format(this->format_ctx, 0, NULL, 1);

// Begin the output by writing the container header
avformat_write_header(this->format_ctx, NULL);

AVFormatContext* ac[] = { this->format_ctx };
print_sdp(ac, 1);
}

FMVStream::~FMVStream()
{
av_write_trailer(this->format_ctx);
avcodec_close(this->stream->codec);

av_free(this->format_ctx);
av_free(this->pic);
av_free(this->format);
av_free(this->stream);
av_free(this->io_ctx);

delete[] this->outbuf;
}

void FMVStream::AllocPicture(PixelFormat pix_fmt, int width, int height)
{
// Allocate a frame
this->pic = av_frame_alloc();

if (pic == nullptr)
{
throw std::runtime_error("avcodec_alloc_frame failed");
}

if (av_image_alloc(pic->data, pic->linesize, width, height, pix_fmt, 1) < 0)
{
throw std::runtime_error("av_image_alloc failed");
}

pic->width = width;
pic->height = height;
}

void FMVStream::print_sdp(AVFormatContext **avc, int n)
{
char sdp[2048];
av_sdp_create(avc, n, sdp, sizeof(sdp));
printf("SDP:\n%s\n", sdp);
fflush(stdout);
}

AVStream* FMVStream::CreateVideoStream(AVFormatContext *oc)
{
AVStream* st = avformat_new_stream(oc, NULL);

if (st == nullptr)
{
std::runtime_error("Could not alloc stream");
}

AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);

if (codec == nullptr)
{
throw std::runtime_error("couldn't find mpeg2 encoder");
}

st->codec = avcodec_alloc_context3(codec);

st->codec->codec_id = AV_CODEC_ID_H264;
st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
// c->bit_rate = 100000;
st->codec->width = this->frameWidth;
st->codec->height = this->frameHeight;
//st->codec->time_base.num = 1;
//st->codec->time_base.den = 25;
st->codec->max_b_frames = 1;
st->codec->pix_fmt = PIX_FMT_YUV420P;

if (oc->oformat->flags & AVFMT_GLOBALHEADER)
{
st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}

// option setup for the codec
av_opt_set(st->codec->priv_data, "profile", "baseline", AV_OPT_SEARCH_CHILDREN);

if (avcodec_open2(st->codec, codec, NULL) < 0)
{
throw std::runtime_error("avcodec_open failed");
}

return st;
}


This block is attempting to convert from the raw RGB to our needed YUV format for h.264 encoding.



void FMVStream::CopyFrameData(const uint8_t* data)
{
AVFrame* inputFrame = nullptr;

// setup the input frame coming in as RGB (hopefully 1 byte per color)
inputFrame = av_frame_alloc();
//inputFrame->type = AVMEDIA_TYPE_VIDEO;
inputFrame->format = PIX_FMT_RGB24;
inputFrame->width = this->stream->codec->width;
inputFrame->height = this->stream->codec->height;

// set the converted frame setup, this will hold our YUV converted raw picture frame
//convertedFrame = av_frame_alloc();
this->pic->format = PIX_FMT_YUV420P;
this->pic->width = this->stream->codec->width;
this->pic->height = this->stream->codec->height;

// allocate the necessary memory for the frame TODO may need to revisit the last argument here for alignment.
auto ret = av_image_alloc(inputFrame->data, inputFrame->linesize, this->stream->codec->width, this->stream->codec->height, (AVPixelFormat)inputFrame->format, 32);

if (ret < 0)
{
std::cerr << "Could not allocate raw picture buffer for input buffer" << std::endl;
}

ret = av_image_alloc(this->pic->data, this->pic->linesize, this->stream->codec->width, this->stream->codec->height, this->stream->codec->pix_fmt, 32);

if (ret < 0)
{
std::cerr << "Could not allocate raw picture buffer for input buffer" << std::endl;
}

// setup the timestamp stepping
this->pic->pts = static_cast<int64_t>(this->stream->codec->frame_number);

// allocating outbuffer
int nbytes = avpicture_get_size(PIX_FMT_YUV420P, this->stream->codec->width, this->stream->codec->height);
uint8_t* outbuffer = (uint8_t*)av_malloc(nbytes*sizeof(uint8_t));

// fill image with input screenshot
avpicture_fill((AVPicture*)inputFrame, (uint8_t*)data, PIX_FMT_RGB24, this->stream->codec->width, this->stream->codec->height);

// clear output picture for buffer copy
avpicture_fill((AVPicture*)this->pic, outbuffer, PIX_FMT_YUV420P, this->stream->codec->width, this->stream->codec->height);

// convert the RGB frame to a YUV frame using the sws Context
SwsContext* swsCtx = sws_getContext(this->stream->codec->width, this->stream->codec->height, PIX_FMT_RGB32, this->stream->codec->width, this->stream->codec->height, PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);

// use the scale function to transcode this raw frame to the correct type
sws_scale(swsCtx, inputFrame->data, inputFrame->linesize, 0, this->stream->codec->height, this->pic->data, this->pic->linesize);

av_free(inputFrame);
av_free(swsCtx);
}


This is the block that encodes the raw data to h.264, and then send it out the Mpeg2 ts. I believe the problem lies within this block. I can put a break point in my write frame block and see that frames are being written, however, opening the resulting file in VLC results in a blank video. The file is approx 2Mb.



void FMVStream::EncodeFrame(const uint8_t* data)
{
AVCodecContext* c = this->stream->codec;

// Convert and load the frame data into the AVFrame struct
CopyFrameData(data);

AVPacket pkt = { 0 };
av_init_packet(&pkt);
pkt.pts = this->pic->pts;

int outPacket, out_size;

out_size = avcodec_encode_video2(c, &pkt, this->pic, &outPacket);

pkt.flags |= AV_PKT_FLAG_KEY;
pkt.stream_index = this->stream->index;

this->pic->pts += av_rescale_q(1, this->stream->codec->time_base, this->stream->time_base);
//pkt.pts = av_rescale_q(1, this->stream->codec->time_base, this->stream->time_base);

// if zero size, it means the image was buffered
if(out_size == 0 && outPacket > 0)
{
/* write the compressed frame in the media file */
int ret = av_interleaved_write_frame(this->format_ctx, &pkt);

if(ret != 0)
{
throw std::runtime_error("Failed to write frame");
}
}
else if (out_size < 0)
{
throw std::runtime_error("Failed to encode frame");
}

++this->frame_count;
av_free_packet(&pkt);
}

void FMVStream::setFrameWidth(const int width)
{
this->frameWidth = width;
}

int FMVStream::getFrameWidth() const
{
return this->frameWidth;
}

void FMVStream::setFrameHeight(const int height)
{
this->frameHeight = height;
}

int FMVStream::getFrameHeight() const
{
return this->frameHeight;
}

void FMVStream::setStreamAddress(const std::string& address)
{
this->streamFilename = address;
}

std::string FMVStream::getStreamAddress() const
{
return this->streamFilename;
}


int main(int argc, char** argv)
{
FMVStream* fmv = new FMVStream;

fmv->setFrameWidth(640);
fmv->setFrameHeight(480);

std::cout << "Streaming Address: " << fmv->getStreamAddress() << std::endl;

// create our alternating frame of black and white to test the streaming functionality
uint8_t white[640 * 480 * 3];
uint8_t black[640 * 480 * 3];

std::memset(white, 255, 640 * 480 * 3);
std::memset(black, 0, 640 * 480 * 3);

for (auto i = 0; i < 1000; i++)
{
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);
fmv->EncodeFrame(white);

//fmv->EncodeFrame(black);
//fmv->EncodeFrame(black);
//fmv->EncodeFrame(black);
//fmv->EncodeFrame(black);
//fmv->EncodeFrame(black);
//fmv->EncodeFrame(black);
//fmv->EncodeFrame(black);
//fmv->EncodeFrame(black);
//fmv->EncodeFrame(black);
//fmv->EncodeFrame(black);
}

delete fmv;
}


Here is the resultant output via the console / my print SDP function.



[libx264 @ 000000298ec9bd60] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2
AVX FMA3 AVX2 LZCNT BMI2
[libx264 @ 000000298ec9bd60] profile Constrained Baseline, level 3.0
Output #0, mpegts, to '(null)':
Stream #0:0: Video: h264 (libx264), yuv420p, 640x480, q=-1--1
SDP:
v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
t=0 0
a=tool:libavformat 56.23.104
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1
a=control:streamid=0

Streaming Address: test.mpeg
[libx264 @ 000000298ec9bd60] frame I:5 Avg QP: 8.69 size: 1109
[libx264 @ 000000298ec9bd60] frame P:1048 Avg QP:11.00 size: 11
[libx264 @ 000000298ec9bd60] mb I I16..4: 99.9% 0.0% 0.1%
[libx264 @ 000000298ec9bd60] mb P I16..4: 0.0% 0.0% 0.0% P16..4: 0.0% 0.0
% 0.0% 0.0% 0.0% skip:100.0%
[libx264 @ 000000298ec9bd60] coded y,uvDC,uvAC intra: 0.0% 0.0% 0.0% inter: 0.0%
0.0% 0.0%
[libx264 @ 000000298ec9bd60] i16 v,h,dc,p: 96% 1% 3% 0%
[libx264 @ 000000298ec9bd60] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 0% 19% 81% 0% 0%
0% 0% 0% 0%
[libx264 @ 000000298ec9bd60] i8c dc,h,v,p: 100% 0% 0% 0%
[libx264 @ 000000298ec9bd60] kb/s:3.24


I know there are probably many issues with this program, I am very new with FFMPEG and multimedia programming in general. Ive used many pieces of code found through searching google/ stack overflow to get to this point as is. The resulting output bandwidth is very low, along with the fact that when directed to a flat file instead of udp streaming, the file has a good size but comes up as length 0 tells me that my time stamping must be broken between the frames / pkts, but I am unsure on how to fix this issue.


Aucun commentaire:

Enregistrer un commentaire