Created
September 5, 2013 18:12
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 = ¶ms; | |
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,¶ms); | |
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; | |
} | |
Hi Roxlu, I'd like to second S4nshinez gratitude, and request that you post the missing files. Regards, David
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
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