-
-
Save t-bltg/ad2d92211ccc244dce0331e08cace95f to your computer and use it in GitHub Desktop.
X264 encoder example
This file contains 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 <roxlu/core/Log.h> | |
#include <roxlu/core/Utils.h> | |
#include <video/X264Encoder.h> | |
X264Encoder::X264Encoder() | |
:in_width(0) | |
,in_height(0) | |
,in_pixel_format(AV_PIX_FMT_NONE) | |
,out_width(0) | |
,out_height(0) | |
,out_pixel_format(AV_PIX_FMT_NONE) | |
,fps(25) | |
,fp(NULL) | |
,encoder(NULL) | |
,sws(NULL) | |
,num_nals(0) | |
,pts(0) | |
{ | |
memset((char*)&pic_raw, 0, sizeof(pic_raw)); | |
} | |
X264Encoder::~X264Encoder() { | |
if(sws) { | |
close(); | |
} | |
} | |
bool X264Encoder::open(std::string filename, bool datapath) { | |
if(!validateSettings()) { | |
return false; | |
} | |
int r = 0; | |
int nheader = 0; | |
int header_size = 0; | |
// @todo add validate which checks if all params are set (in/out width/height, fps,etc..); | |
if(encoder) { | |
RX_ERROR("Already opened. first call close()"); | |
return false; | |
} | |
if(out_pixel_format != AV_PIX_FMT_YUV420P) { | |
RX_ERROR("At this moment the output format must be AV_PIX_FMT_YUV420P"); | |
return false; | |
} | |
sws = sws_getContext(in_width, in_height, in_pixel_format, | |
out_width, out_height, out_pixel_format, | |
SWS_FAST_BILINEAR, NULL, NULL, NULL); | |
if(!sws) { | |
RX_ERROR("Cannot create SWS context"); | |
::exit(EXIT_FAILURE); | |
} | |
if(datapath) { | |
filename = rx_to_data_path(filename); | |
} | |
fp = fopen(filename.c_str(), "w+b"); | |
if(!fp) { | |
RX_ERROR("Cannot open the h264 destination file"); | |
goto error; | |
} | |
x264_picture_alloc(&pic_in, X264_CSP_I420, out_width, out_height); | |
setParams(); | |
// create the encoder using our params | |
encoder = x264_encoder_open(¶ms); | |
if(!encoder) { | |
RX_ERROR("Cannot open the encoder"); | |
goto error; | |
} | |
// write headers | |
r = x264_encoder_headers(encoder, &nals, &nheader); | |
if(r < 0) { | |
RX_ERROR("x264_encoder_headers() failed"); | |
goto error; | |
} | |
header_size = nals[0].i_payload + nals[1].i_payload +nals[2].i_payload; | |
if(!fwrite(nals[0].p_payload, header_size, 1, fp)) { | |
RX_ERROR("Cannot write headers"); | |
goto error; | |
} | |
pts = 0; | |
return true; | |
error: | |
close(); | |
return false; | |
} | |
bool X264Encoder::encode(char* pixels) { | |
if(!sws) { | |
RX_ERROR("Not initialized, so cannot encode"); | |
return false; | |
} | |
// copy the pixels into our "raw input" container. | |
int bytes_filled = avpicture_fill(&pic_raw, (uint8_t*)pixels, in_pixel_format, in_width, in_height); | |
if(!bytes_filled) { | |
RX_ERROR("Cannot fill the raw input buffer"); | |
return false; | |
} | |
// convert to I420 for x264 | |
int h = sws_scale(sws, pic_raw.data, pic_raw.linesize, 0, | |
in_height, pic_in.img.plane, pic_in.img.i_stride); | |
if(h != out_height) { | |
RX_ERROR("scale failed: %d", h); | |
return false; | |
} | |
// and encode and store into pic_out | |
pic_in.i_pts = pts; | |
int frame_size = x264_encoder_encode(encoder, &nals, &num_nals, &pic_in, &pic_out); | |
if(frame_size) { | |
if(!fwrite(nals[0].p_payload, frame_size, 1, fp)) { | |
RX_ERROR("Error while trying to write nal"); | |
return false; | |
} | |
} | |
++pts; | |
return true; | |
} | |
bool X264Encoder::close() { | |
if(encoder) { | |
x264_picture_clean(&pic_in); | |
memset((char*)&pic_in, 0, sizeof(pic_in)); | |
memset((char*)&pic_out, 0, sizeof(pic_out)); | |
x264_encoder_close(encoder); | |
encoder = NULL; | |
} | |
if(sws) { | |
sws_freeContext(sws); | |
sws = NULL; | |
} | |
memset((char*)&pic_raw, 0, sizeof(pic_raw)); | |
if(fp) { | |
fclose(fp); | |
fp = NULL; | |
} | |
return true; | |
} | |
void X264Encoder::setParams() { | |
x264_param_default_preset(¶ms, "ultrafast", "zerolatency"); | |
params.i_threads = 1; | |
params.i_width = out_width; | |
params.i_height = out_height; | |
params.i_fps_num = fps; | |
params.i_fps_den = 1; | |
} | |
bool X264Encoder::validateSettings() { | |
if(!in_width) { | |
RX_ERROR("No in_width set"); | |
return false; | |
} | |
if(!in_height) { | |
RX_ERROR("No in_height set"); | |
return false; | |
} | |
if(!out_width) { | |
RX_ERROR("No out_width set"); | |
return false; | |
} | |
if(!out_height) { | |
RX_ERROR("No out_height set"); | |
return false; | |
} | |
if(in_pixel_format == AV_PIX_FMT_NONE) { | |
RX_ERROR("No in_pixel_format set"); | |
return false; | |
} | |
if(out_pixel_format == AV_PIX_FMT_NONE) { | |
RX_ERROR("No out_pixel_format set"); | |
return false; | |
} | |
return true; | |
} | |
This file contains 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
/* | |
# X264Encoder | |
Simple wrapper for x264 that you can use to encode into x264. Make | |
sure to set all the params before calling `open()`. See below for the | |
members that the you need to set. | |
*/ | |
#ifndef ROXLU_X264_ENCODER_H | |
#define ROXLU_X264_ENCODER_H | |
#include <inttypes.h> | |
#include <stdio.h> | |
#include <string> | |
extern "C" { | |
# include <x264.h> | |
# include <libswscale/swscale.h> | |
# include <libavcodec/avcodec.h> | |
} | |
class X264Encoder { | |
public: | |
X264Encoder(); | |
~X264Encoder(); | |
bool open(std::string filename, bool datapath); /* open for encoding */ | |
bool encode(char* pixels); /* encode the given data */ | |
bool close(); /* close the encoder and file, frees all memory */ | |
private: | |
bool validateSettings(); /* validates if all params are set correctly, like width,height, etc.. */ | |
void setParams(); /* sets the x264 params */ | |
public: | |
/* params the user should set */ | |
int in_width; | |
int in_height; | |
int out_width; | |
int out_height; | |
int fps; /* e.g. 25, 60, etc.. */ | |
AVPixelFormat in_pixel_format; | |
AVPixelFormat out_pixel_format; | |
/* x264 */ | |
AVPicture pic_raw; /* used for our "raw" input container */ | |
x264_picture_t pic_in; | |
x264_picture_t pic_out; | |
x264_param_t params; | |
x264_nal_t* nals; | |
x264_t* encoder; | |
int num_nals; | |
/* input / output */ | |
int pts; | |
struct SwsContext* sws; | |
FILE* fp; | |
}; | |
#endif |
This file contains 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 <video/X264EncoderThreaded.h> | |
// ---------------------------------------------------------------- | |
void x264_encoder_thread(void* user) { | |
X264EncoderThreaded& enc_threaded = *(static_cast<X264EncoderThreaded*>(user)); | |
X264Encoder& enc = enc_threaded.enc; | |
char* frame = NULL; | |
AVPicture pic; | |
int num_bytes = avpicture_fill(&pic, NULL, enc.in_pixel_format, enc.in_width, enc.in_height); | |
frame = new char[num_bytes * 2]; // lib swscale needs some more space | |
std::vector<X264Data> work_data; | |
while(!enc_threaded.must_stop) { | |
// get data we need to encode | |
enc_threaded.lock(); | |
{ | |
std::copy(enc_threaded.input_data.begin(), enc_threaded.input_data.end(), std::back_inserter(work_data)); | |
enc_threaded.input_data.clear(); | |
} | |
enc_threaded.unlock(); | |
// And encode | |
for(std::vector<X264Data>::iterator it = work_data.begin(); it != work_data.end(); ++it) { | |
X264Data& data = *it; | |
enc_threaded.buffer.read(frame, data.nbytes); | |
enc.encode(frame); | |
} | |
work_data.clear(); | |
} | |
enc.close(); | |
delete[] frame; | |
frame = NULL; | |
} | |
// ---------------------------------------------------------------- | |
X264EncoderThreaded::X264EncoderThreaded() | |
:must_stop(false) | |
,buffer(1024 * 1024 * 512) /* 300 mb, must be enough for quite some frames */ | |
,num_bytes(0) | |
{ | |
uv_mutex_init(&mutex); | |
} | |
X264EncoderThreaded::~X264EncoderThreaded() { | |
stop(); | |
uv_thread_join(&thread_id); | |
uv_mutex_destroy(&mutex); | |
} | |
bool X264EncoderThreaded::start(std::string filename, bool datapath) { | |
if(!enc.open(filename, datapath)) { | |
return false; | |
} | |
// get the size for one frame. | |
AVPicture pic; | |
num_bytes = avpicture_fill(&pic, NULL, enc.in_pixel_format, enc.in_width, enc.in_height); | |
uv_thread_create(&thread_id, x264_encoder_thread, this); | |
return true; | |
} | |
void X264EncoderThreaded::encode(char* pixels) { | |
X264Data data; | |
data.offset = buffer.getWriteIndex(); | |
data.nbytes = num_bytes; | |
size_t written = buffer.write((const char*)pixels, num_bytes); | |
lock(); | |
input_data.push_back(data); | |
unlock(); | |
//RX_VERBOSE("WRITTEN %ld, write index: %ld", written, buffer.getWriteIndex()); | |
} |
This file contains 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
#ifndef ROXLU_X264_ENCODER_THREADED_H | |
#define ROXLU_X264_ENCODER_THREADED_H | |
extern "C" { | |
# include <uv.h> | |
} | |
#include <string> | |
#include <vector> | |
#include <roxlu/Roxlu.h> /* we need the RingBuffer */ | |
#include <video/X264Encoder.h> | |
void x264_encoder_thread(void* user); | |
struct X264Data { | |
size_t offset; | |
size_t nbytes; | |
}; | |
class X264EncoderThreaded { | |
public: | |
X264EncoderThreaded(); | |
~X264EncoderThreaded(); | |
bool start(std::string filename, bool datapath); | |
void encode(char* pixels); | |
void stop(); | |
void lock(); | |
void unlock(); | |
public: | |
RingBuffer buffer; | |
X264Encoder enc; /* the encoder we use to encode to h264 */ | |
uv_thread_t thread_id; | |
uv_mutex_t mutex; | |
size_t num_bytes; /* num bytes per frame */ | |
volatile bool must_stop; | |
std::vector<X264Data> input_data; | |
}; | |
inline void X264EncoderThreaded::lock() { | |
uv_mutex_lock(&mutex); | |
} | |
inline void X264EncoderThreaded::unlock() { | |
uv_mutex_unlock(&mutex); | |
} | |
inline void X264EncoderThreaded::stop() { | |
must_stop = true; | |
} | |
#endif |
This file contains 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 <video/YUV420P.h> | |
YUV420P::YUV420P() { | |
memset(linesize, 0, sizeof(linesize)); | |
memset(width, 0, sizeof(width)); | |
memset(height, 0, sizeof(height)); | |
} | |
YUV420P::~YUV420P() { | |
} | |
//bool YUV420P::setup(VideoBuffer* vb) { | |
//bool YUV420P::setup(AVDecoderFrame* f) { | |
bool YUV420P::setup(AVFrame* ff) { | |
if(!ff) { | |
RX_ERROR("Invalid AVFrame"); | |
return false; | |
} | |
// setup width/height/linesizes | |
width[0] = ff->width; // Y | |
width[1] = ff->width * 0.5; // U | |
width[2] = ff->width * 0.5; // V | |
height[0] = ff->height; // Y | |
height[1] = ff->height * 0.5; // U | |
height[2] = ff->height * 0.5; // V | |
linesize[0] = ff->linesize[0]; // Y | |
linesize[1] = ff->linesize[1]; // U | |
linesize[2] = ff->linesize[2]; // V | |
// validate | |
if(!width[0] || !width[1] || !width[2]) { | |
RX_ERROR("Cannot validate width"); | |
return false; | |
} | |
if(!height[0] || !height[1] || !height[2]) { | |
RX_ERROR("Cannot validate height"); | |
return false; | |
} | |
if(!linesize[0] || !linesize[1] || !linesize[2]) { | |
RX_ERROR("Cannot validate linesize"); | |
return false; | |
} | |
// Setup video textures | |
if(!vt_y.setup(width[0], height[0], GL_R8, GL_RED, GL_UNSIGNED_BYTE, linesize[0])) { | |
RX_ERROR("Cannot setup the VideoTexture for the Y plane"); | |
return false; | |
} | |
if(!vt_u.setup(width[1], height[1], GL_R8, GL_RED, GL_UNSIGNED_BYTE, linesize[1])) { | |
RX_ERROR("Cannot setup the VideoTexture for the U plane"); | |
return false; | |
} | |
if(!vt_v.setup(width[2], height[2], GL_R8, GL_RED, GL_UNSIGNED_BYTE, linesize[2])) { | |
RX_ERROR("Cannot setup the VideoTexture for the V plane"); | |
return false; | |
} | |
print(); | |
return true; | |
} | |
bool YUV420P::updateTextures(AVFrame* ff) { | |
if(!ff) { | |
RX_ERROR("Invalid AVFrame"); | |
return false; | |
} | |
if(!ff->width || !ff->height) { | |
RX_ERROR("Invalid width / height for the given AVDecoderFrame: %d x %d", ff->width, ff->height); | |
return false; | |
} | |
vt_y.setPixels((char*)ff->data[0]); | |
vt_u.setPixels((char*)ff->data[1]); | |
vt_v.setPixels((char*)ff->data[2]); | |
return true; | |
} | |
void YUV420P::print() { | |
RX_VERBOSE("yuv, plane 0: width: %d, height: %d, stride: %d", width[0], height[0], linesize[0]); | |
RX_VERBOSE("yuv, plane 1: width: %d, height: %d, stride: %d", width[1], height[1], linesize[1]); | |
RX_VERBOSE("yuv, plane 2: width: %d, height: %d, stride: %d", width[2], height[2], linesize[2]); | |
} |
This file contains 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
/* | |
# YUV420P | |
This class is a basic container for YUV420P based video data. | |
We assume the given VideoBuffer contains AVDecoderFrames/AVFrames | |
of the type AV_PIX_FMT_YUV420P. | |
This classes uses 3 fast VideoTextures to stream video data to the | |
GPU (using round robbin', orphaned pbos) | |
*/ | |
#ifndef ROXLU_YUV420P_H | |
#define ROXLU_YUV420P_H | |
#include <roxlu/core/Log.h> | |
#include <roxlu/opengl/GL.h> | |
#include <av/AVDecoder.h> | |
#include <video/VideoTexture.h> | |
class YUV420P { | |
public: | |
YUV420P(); | |
~YUV420P(); | |
bool setup(AVFrame* f); | |
void print(); /* print debug info */ | |
bool updateTextures(AVFrame* f); | |
GLuint getYTexture(); | |
GLuint getUTexture(); | |
GLuint getVTexture(); | |
public: | |
int linesize[3]; /* strides of the 3 planes */ | |
int width[3]; /* widths of the 3 planes */ | |
int height[3]; /* heigts of the 3 planes */ | |
VideoTexture vt_y; | |
VideoTexture vt_u; | |
VideoTexture vt_v; | |
}; | |
inline GLuint YUV420P::getYTexture() { | |
return vt_y.getTexture(); | |
} | |
inline GLuint YUV420P::getUTexture() { | |
return vt_u.getTexture(); | |
} | |
inline GLuint YUV420P::getVTexture() { | |
return vt_v.getTexture(); | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment