Skip to content

Instantly share code, notes, and snippets.

@t-bltg
Forked from roxlu/X264Encoder.cpp
Created November 17, 2017 15:37
Show Gist options
  • Save t-bltg/ad2d92211ccc244dce0331e08cace95f to your computer and use it in GitHub Desktop.
Save t-bltg/ad2d92211ccc244dce0331e08cace95f to your computer and use it in GitHub Desktop.
X264 encoder example
#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(&params);
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(&params, "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;
}
/*
# 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
#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());
}
#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
#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]);
}
/*
# 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