Skip to content

Instantly share code, notes, and snippets.

@roxlu
Created September 5, 2013 18:12
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save roxlu/6453908 to your computer and use it in GitHub Desktop.
Save roxlu/6453908 to your computer and use it in GitHub Desktop.
Example usage of X264. We create a raw YUV pattern and encode it with x264.
#include <stdio.h>
#include <core/BitStream.h>
#include <core/VideoEncoder.h>
#include <core/TestPattern.h>
#include <flv/FLVReader.h>
#define TEST_FLV_READER 0
int main() {
#if TEST_FLV_READER
BitStream bs;
bs.loadFile("./barsandtone.flv");
FLVReader flv(bs);
flv.parse();
#endif
VideoSettings vs;
vs.width = 320;
vs.height = 240;
vs.fps = 20;
VideoEncoder ve;
if(!ve.setup(vs)) {
printf("error: cannot setup the VideoEncoder");
return EXIT_FAILURE;
}
if(!ve.initialize()) {
printf("error: cannot intialize the VideoEncoder");
return EXIT_FAILURE;
}
std::vector<uint8_t> test_frame;
TestPattern tp;
tp.openFile("test_frames/raw.yuv");
tp.setup(vs.width, vs.height, 24);
FLVTag flv_tag;
AVPacket pkt;
pkt.allocate(vs.width * vs.height + (vs.width * vs.height / 4) * 2);
ve.openFile("test_frames/raw.h264");
for(int i = 0; i < 240; ++i) {
tp.generateFrame(pkt.data);
tp.writeFrameToFile(pkt.data);
ve.encodePacket(&pkt, flv_tag);
ve.writeTagToFile(flv_tag);
}
ve.closeFile();
return 1;
}
#include <cmath>
#include <assert.h>
#include <core/TestPattern.h>
TestPattern::TestPattern()
:w(0)
,h(0)
,frame_num(0)
,duration(0)
{
}
TestPattern::~TestPattern() {
if(ofs.is_open()) {
ofs.close();
}
}
bool TestPattern::setup(int width, int height, int framespersec) {
assert(width > 0);
assert(height > 0);
assert(framespersec > 0);
w = width;
h = height;
fps = framespersec;
return true;
}
void TestPattern::generateFrame(std::vector<uint8_t>& result) {
int y_bytes = w * h;
int uv_bytes = w * h / 4;
result.assign( y_bytes + uv_bytes + uv_bytes, 0x00 );
// Y channel
int POT = 3;
for(int i = 0; i < w; ++i) {
for(int j = 0; j < h; ++j) {
int dx = j * w + i;
result[dx] = (((i ^ j) >> POT) & 1) - 1; // checkerbord
}
}
// U channel
duration = float(frame_num)/fps;
float t = 0.5 + 0.5 * sin(duration * 6.2831);
int offset = y_bytes;
for(int i = 0; i < uv_bytes; ++i) {
int dx = offset + i;
result[dx] = t * 0xFF;
}
// V channel
offset = y_bytes + uv_bytes;
for(int i = 0; i < uv_bytes; ++i) {
int dx = offset + i;
result[dx] = (1.0 - t) * 0xFF;
}
++frame_num;
}
bool TestPattern::openFile(std::string path) {
ofs.open(path.c_str(), std::ios::out | std::ios::binary);
if(!ofs.is_open()) {
printf("error: cannot open: %s\n", path.c_str());
return false;
}
return true;
}
bool TestPattern::writeFrameToFile(std::vector<uint8_t>& result) {
if(!ofs.is_open()) {
printf("error: the file is not opened! call openFile first.\n");
return false;
}
ofs.write((char*)&result.front(), result.size());
return true;
}
/*
# Test Pattern
Generates a test YUV/I420p pattern
When you call `openFile()` and `writeFrameToFile()` you can use avconv to
convert the raw yuv file into a video using:
````sh
./avconv -f rawvideo -s 320x240 -i raw.yuv -r 10 -vcodec h264 out.mov
````
*/
#ifndef ROXLU_TEST_PATTERN_H
#define ROXLU_TEST_PATTERN_H
#include <inttypes.h>
#include <vector>
#include <fstream>
class TestPattern {
public:
TestPattern();
~TestPattern();
bool setup(int width, int height, int fps);
bool openFile(std::string file);
void generateFrame(std::vector<uint8_t>& result);
bool writeFrameToFile(std::vector<uint8_t>& result);
private:
int w;
int h;
int fps;
unsigned long frame_num;
std::ofstream ofs;
float duration;
};
#endif
#include <assert.h>
#include <core/VideoEncoder.h>
#include <core/Debug.h>
VideoEncoder::VideoEncoder()
:encoder(NULL)
,vflip(true)
,frame_num(0)
{
}
VideoEncoder::~VideoEncoder() {
if(ofs.is_open()) {
closeFile();
}
}
// @todo check if width / height are valid (multiples of 16 I believe)
bool VideoEncoder::setup(VideoSettings s) {
assert(s.width > 0);
assert(s.height > 0);
assert(s.fps > 0);
settings = s;
return true;
}
bool VideoEncoder::initialize() {
assert(settings.width);
assert(settings.height);
if(!initializeX264()) {
return false;
}
if(!initializePic()) {
return false;
}
return true;
}
bool VideoEncoder::initializeX264() {
assert(settings.width > 0);
assert(settings.height > 0);
assert(settings.fps > 0);
int r = 0;
x264_param_t* p = &params;
r = x264_param_default_preset(p, "ultrafast", "zerolatency");
if(r != 0) {
printf("error: cannot set the default preset on x264.\n");
return false;
}
#if !defined(NDEBUG)
p->i_log_level = X264_LOG_DEBUG;
#endif
p->i_threads = 1;
p->i_width = settings.width;
p->i_height = settings.height;
p->i_fps_num = settings.fps;
p->i_fps_den = 1;
p->b_annexb = 1;
p->b_repeat_headers = 1; // @todo - test this, we might want SPS/PPS before every keyframe! (when we set b_repeat_headers to 0 and convert the raw .h264 to .mov (with avconv) the resulting mov is faulty)
p->i_keyint_max = settings.fps; // @todo - when writing raw frames to an .h264 file we need this else the video is going to be garbage
#if 0
// tmp
p->rc.i_rc_method = X264_RC_CRF;
p->rc.f_rf_constant = 25;
p->rc.f_rf_constant_max = 45;
#endif
// end
r = x264_param_apply_profile(p, "baseline");
if(r != 0) {
printf("error: cannot set the baseline profile on x264.\n");
return false;
}
encoder = x264_encoder_open(p);
if(!encoder) {
printf("error: cannot create the encoder.\n");
return false;
}
x264_encoder_parameters(encoder,&params);
print_x264_params(p);
return true;
}
bool VideoEncoder::initializePic() {
unsigned int csp = (vflip) ? X264_CSP_I420 | X264_CSP_VFLIP : X264_CSP_I420;
#if 0
int r = x264_picture_alloc(&pic_out, csp, settings.width, settings.height);
if(r != 0) {
printf("error: cannot allocate the pic_out.\n");
return false;
}
#endif
x264_picture_init(&pic_in);
pic_in.img.i_csp = csp;
pic_in.img.i_stride[0] = settings.width;
pic_in.img.i_stride[1] = settings.width >> 1;
pic_in.img.i_stride[2] = settings.width >> 1;
pic_in.img.i_plane = 3;
return true;
}
bool VideoEncoder::encodePacket(AVPacket* p, FLVTag& tag) {
assert(p);
assert(encoder);
size_t nbytes_y = settings.width * settings.height;
size_t nbytes_uv = nbytes_y / 4;
pic_in.img.plane[0] = &p->data.front();
pic_in.img.plane[1] = &p->data[nbytes_y];
pic_in.img.plane[2] = &p->data[nbytes_y + nbytes_uv];
pic_in.i_pts = frame_num;
frame_num++;
x264_nal_t* nal = NULL;
int nals_count = 0;
int frame_size = x264_encoder_encode(encoder, &nal, &nals_count, &pic_in, &pic_out);
if(frame_size < 0) {
printf("error: x264_encoder_encode failed.\n");
return false;
}
if(!nal) {
printf("error: x264_encoder_encode returned no valid nals.\n");
return false;
}
tag.timestamp = p->timestamp;
tag.data = nal[0].p_payload;
tag.size = frame_size;
return true;
}
bool VideoEncoder::openFile(std::string filepath) {
if(ofs.is_open()) {
printf("error: cannot open the video encoder output file becuase it's already open.\n");
return false;
}
ofs.open(filepath.c_str(), std::ios::binary | std::ios::out);
if(!ofs.is_open()) {
printf("error: cannot open the video encoder output file: %s\n", filepath.c_str());
return false;
}
return true;
}
bool VideoEncoder::writeTagToFile(FLVTag& tag) {
if(!ofs.is_open()) {
printf("error: cannot write the video tag to the encoder output file because the file hasn't been opened yet.\n");
return false;
}
ofs.write((char*)tag.data, tag.size);
return true;
}
bool VideoEncoder::closeFile() {
if(!ofs.is_open()) {
printf("error: cannot close the encoder file because it hasn't been opened yet.\n");
return false;
}
ofs.close();
return true;
}
@S4nshinez
Copy link

Hi Roxlu,
thank you for sharing! That was very helpful to me, I am also trying to interface x264 with raw YUV data directly in C.

I still have problems extracting the required code sections from your example. Could you add the missing files so that compilation is possible? Or change the code that it can be compiled directly without the required files?

Whats missing is:
VideoEncoder.h
Debug.h
BitStream.h
FLVReader.h

I could contribute a CMake file to compile it out of the box if you like.

Thx!

Sanshi

Copy link

ghost commented Nov 26, 2013

Hi Roxlu, I'd like to second S4nshinez gratitude, and request that you post the missing files. Regards, David

@HristoDimitrov
Copy link

Hi S4nshinez and amdijefri,
I found those missing files in another of Roxlu's repositories.

This is the link: https://github.com/roxlu/video_streamer/tree/master/projects/streamer/include/streamer/core
And FLVReader.h is in https://github.com/roxlu/video_streamer/tree/master/projects/streamer/include/streamer/flv

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment