Skip to content

Commit

Permalink
let user specify pixel format for uploaded data (#16)
Browse files Browse the repository at this point in the history
-only hardware supported formats will work
-NULL will initialize for NV12
-also added documentation for new feature
  • Loading branch information
bmegli authored Feb 11, 2019
1 parent 2321497 commit 01acde6
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 43 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,13 @@ There are just 4 functions and 3 user-visible data types:
- `hve_receive_packet` (retrieves compressed data from hardware)
- `hve_close`

The library takes off you the burden of:
- hardware encoder initialization/cleanup
- internal data lifecycle managegment

```C
struct hve_config hardware_config = {WIDTH, HEIGHT, FRAMERATE, DEVICE};
struct hve_config hardware_config = {WIDTH, HEIGHT, FRAMERATE, DEVICE, PIXEL_FORMAT};
struct hve *hardware_encoder=hve_init(&hardware_config);
struct hve_frame frame = { 0 };

//later assuming PIXEL_FORMAT is "nv12" (you can use something else)

//fill with your stride (width including padding if any)
frame.linesize[0] = frame.linesize[1] = WIDTH;

Expand Down
48 changes: 26 additions & 22 deletions examples/hve_encode_raw_h264.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
*/

#include <stdio.h> //printf, fprintf
#include <inttypes.h> //uint8_t

Expand All @@ -19,6 +19,7 @@ const int HEIGHT=720;
const int FRAMERATE=30;
int SECONDS=10;
const char *DEVICE=NULL; //NULL for default or device e.g. "/dev/dri/renderD128"
const char *PIXEL_FORMAT="nv12"; //NULL for default (NV12) or pixel format e.g. "rgb0"

int encoding_loop(struct hve *hardware_encoder, FILE *output_file);
int process_user_input(int argc, char* argv[]);
Expand All @@ -30,26 +31,26 @@ int main(int argc, char* argv[])
//get SECONDS and DEVICE from the command line
if( process_user_input(argc, argv) < 0 )
return -1;

//prepare library data
struct hve_config hardware_config = {WIDTH, HEIGHT, FRAMERATE, DEVICE};
struct hve_config hardware_config = {WIDTH, HEIGHT, FRAMERATE, DEVICE, PIXEL_FORMAT};
struct hve *hardware_encoder;

//prepare file for raw H.264 output
FILE *output_file = fopen("output.h264", "w+b");
FILE *output_file = fopen("output.h264", "w+b");
if(output_file == NULL)
return fprintf(stderr, "unable to open file for output\n");

//initialize library with hve_init
if( (hardware_encoder = hve_init(&hardware_config)) == NULL )
{
fclose(output_file);
return hint_user_on_failure(argv);
}

//do the actual encoding
int status = encoding_loop(hardware_encoder, output_file);

hve_close(hardware_encoder);
fclose(output_file);

Expand All @@ -62,8 +63,11 @@ int main(int argc, char* argv[])
int encoding_loop(struct hve *hardware_encoder, FILE *output_file)
{
struct hve_frame frame = { 0 };
int frames=SECONDS*FRAMERATE, f, failed;
int frames=SECONDS*FRAMERATE, f, failed;

//we are working with NV12 because we specified nv12 pixel format
//when calling hve_init, in principle we could use other format
//if hardware supported it (e.g. RGB0 is supported on my Intel)
uint8_t Y[WIDTH*HEIGHT]; //dummy NV12 luminance data
uint8_t color[WIDTH*HEIGHT/2]; //dummy NV12 color data

Expand All @@ -72,21 +76,21 @@ int encoding_loop(struct hve *hardware_encoder, FILE *output_file)

//encoded data is returned in FFmpeg packet
AVPacket *packet;
for(f=0;f<frames;++f)
{

for(f=0;f<frames;++f)
{
//prepare dummy image date, normally you would take it from camera or other source
memset(Y, f % 255, WIDTH*HEIGHT); //NV12 luminance (ride through greyscale)
memset(color, 128, WIDTH*HEIGHT/2); //NV12 UV (no color really)

//fill hve_frame with pointers to your data in NV12 pixel format
frame.data[0]=Y;
frame.data[0]=Y;
frame.data[1]=color;

//encode this frame
if( hve_send_frame(hardware_encoder, &frame) != HVE_OK)
break; //break on error

while( (packet=hve_receive_packet(hardware_encoder, &failed)) )
{
//packet.data is H.264 encoded frame of packet.size length
Expand All @@ -95,12 +99,12 @@ int encoding_loop(struct hve *hardware_encoder, FILE *output_file)
//it could also fail in harsh real world...
fwrite(packet->data, packet->size, 1, output_file);
}

//NULL packet and non-zero failed indicates failure during encoding
if(failed)
if(failed)
break; //break on error
}

//flush the encoder by sending NULL frame, encode some last frames returned from hardware
hve_send_frame(hardware_encoder, NULL);
while( (packet=hve_receive_packet(hardware_encoder, &failed)) )
Expand All @@ -121,10 +125,10 @@ int process_user_input(int argc, char* argv[])
fprintf(stderr, "%s 10 /dev/dri/renderD128\n", argv[0]);
return -1;
}

SECONDS = atoi(argv[1]);
DEVICE=argv[2]; //NULL as last argv argument, or device path

return 0;
}

Expand All @@ -140,4 +144,4 @@ void hint_user_on_success()
printf("output written to \"outout.h264\" file\n");
printf("test with:\n\n");
printf("ffplay output.h264\n");
}
}
41 changes: 26 additions & 15 deletions hve.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
struct hve
{
AVBufferRef* hw_device_ctx;
enum AVPixelFormat sw_pix_fmt;
AVCodecContext* avctx;
AVFrame *sw_frame;
AVFrame *hw_frame;
Expand All @@ -44,7 +45,7 @@ struct hve *hve_init(const struct hve_config *config)
fprintf(stderr, "hve: not enough memory for hve\n");
return NULL;
}
*h = zero_hve; //set all members of dynamically allocated struct to 0 in a portable way
*h = zero_hve; //set all members of dynamically allocated struct to 0 in a portable way

avcodec_register_all();
av_log_set_level(AV_LOG_VERBOSE);
Expand Down Expand Up @@ -74,6 +75,15 @@ struct hve *hve_init(const struct hve_config *config)
h->avctx->sample_aspect_ratio = (AVRational){ 1, 1 };
h->avctx->pix_fmt = AV_PIX_FMT_VAAPI;

//try to find software pixel format that user wants to upload data in
if(config->pixel_format == NULL)
h->sw_pix_fmt = AV_PIX_FMT_NV12;
else if( ( h->sw_pix_fmt = av_get_pix_fmt(config->pixel_format) ) == AV_PIX_FMT_NONE )
{
fprintf(stderr, "hve: failed to find pixel format %s\n", config->pixel_format);
return hve_close_and_return_null(h);
}

if((err = init_hwframes_context(h, config)) < 0)
{
fprintf(stderr, "hve: failed to set hwframe context\n");
Expand All @@ -94,7 +104,7 @@ struct hve *hve_init(const struct hve_config *config)

h->sw_frame->width = config->width;
h->sw_frame->height = config->height;
h->sw_frame->format = AV_PIX_FMT_NV12;
h->sw_frame->format = h->sw_pix_fmt;

av_init_packet(&h->enc_pkt);
h->enc_pkt.data = NULL;
Expand Down Expand Up @@ -135,13 +145,14 @@ static int init_hwframes_context(struct hve* h, const struct hve_config *config)
}
frames_ctx = (AVHWFramesContext*)(hw_frames_ref->data);
frames_ctx->format = AV_PIX_FMT_VAAPI;
frames_ctx->sw_format = AV_PIX_FMT_NV12;
frames_ctx->sw_format = h->sw_pix_fmt;
frames_ctx->width = config->width;
frames_ctx->height = config->height;
frames_ctx->initial_pool_size = 20;
if((err = av_hwframe_ctx_init(hw_frames_ref)) < 0)
{
fprintf(stderr, "hve: failed to initialize VAAPI frame context\n");
fprintf(stderr, "hve: failed to initialize VAAPI frame context - \"%s\"\n", av_err2str(err));
fprintf(stderr, "hve: hint - make sure you are using supported pixel format\n");
av_buffer_unref(&hw_frames_ref);
return HVE_ERROR;
}
Expand All @@ -158,12 +169,12 @@ int hve_send_frame(struct hve *h,struct hve_frame *frame)
AVCodecContext* avctx=h->avctx;
AVFrame *sw_frame=h->sw_frame;
int err = 0;

//note - in case hardware frame preparation fails, the frame is fred:
// - here (this is next user try)
// - or in av_close (this is user decision to terminate)
av_frame_free(&h->hw_frame);

// NULL frame is used for flushing the encoder
if(frame == NULL)
{
Expand All @@ -173,18 +184,18 @@ int hve_send_frame(struct hve *h,struct hve_frame *frame)
return HVE_ERROR;
}
return HVE_OK;
}
}

//this just copies a few ints and pointers, not the actual frame data
memcpy(sw_frame->linesize, frame->linesize, sizeof(frame->linesize));
memcpy(sw_frame->data, frame->data, sizeof(frame->data));

if(!(h->hw_frame = av_frame_alloc()))
{
fprintf(stderr, "hve: av_frame_alloc not enough memory\n");
return HVE_ERROR;
}

if((err = av_hwframe_get_buffer(avctx->hw_frames_ctx, h->hw_frame, 0)) < 0)
{
fprintf(stderr, "hve: av_hwframe_get_buffer error\n");
Expand All @@ -201,8 +212,8 @@ int hve_send_frame(struct hve *h,struct hve_frame *frame)
{
fprintf(stderr, "hve: error while transferring frame data to surface\n");
return HVE_ERROR;
}
}

if((err = avcodec_send_frame(avctx, h->hw_frame)) < 0)
{
fprintf(stderr, "hve: send_frame error\n");
Expand All @@ -224,12 +235,12 @@ AVPacket *hve_receive_packet(struct hve *h, int *error)
//- av_close (user decides to finish in the middle of encoding)
//whichever happens first
int ret = avcodec_receive_packet(h->avctx, &h->enc_pkt);

*error=HVE_OK;

if(ret == 0)
return &h->enc_pkt;

//EAGAIN means that we need to supply more data
//EOF means that we are flushing the decoder and no more data is pending
//otherwise we got an error
Expand Down
28 changes: 27 additions & 1 deletion hve.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,30 @@ struct hve;

/**
* @struct hve_config
* @brief Encoder configuration.
* @brief Encoder configuration
*
* The device can be:
* - NULL (select automatically)
* - point to valid device e.g. "/dev/dri/renderD128" for vaapi
*
* If you have multiple VAAPI devices (e.g. NVidia GPU + Intel) you may have
* to specify Intel directly. NVidia will not work through VAAPI for encoding
* (it works through VAAPI-VDPAU bridge and VDPAU is only for decoding).
*
* The pixel_format (format of what you upload) typically can be:
* - nv12 (this is generally safe choice)
* - yuv420p
* - yuyv422
* - uyvy422
* - yuv422p
* - rgb0
* - bgr0
*
* There are no software color conversions in this library.
*
* For pixel format explanation see:
* <a href="https://ffmpeg.org/doxygen/3.4/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5">FFmpeg pixel formats</a>
*
* @see hve_init
*/
struct hve_config
Expand All @@ -53,6 +76,7 @@ struct hve_config
int height; //!< height of the encoded frames
int framerate; //!< framerate of the encoded video
const char *device; //!< NULL or device, e.g. "/dev/dri/renderD128"
const char *pixel_format; //!< NULL for NV12 or format, e.g. "rgb0", "bgr0", "nv12", "yuv420p"
};

/**
Expand Down Expand Up @@ -113,6 +137,8 @@ void hve_close(struct hve* h);
* After flushing follow with hve_receive_packet to get last encoded frames.
* After flushing it is not possible to reuse the encoder.
*
* The pixel format of the frame should match the one specified in hve_init.
*
* Perfomance hints:
* - don't copy data from your source, just pass the pointers to data planes
*
Expand Down

0 comments on commit 01acde6

Please sign in to comment.