Skip to content

Instantly share code, notes, and snippets.

@roxlu
Created September 5, 2013 18:12
Show Gist options
  • 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;
}
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