mediastream: add VP8 (libvpx) support

parent f7480a4f
......@@ -193,6 +193,25 @@ AC_DEFUN([MS_CHECK_VIDEO],[
[have_theora=no])
fi
AC_ARG_ENABLE(vp8,
[ --disable-vp8 Disable vp8 support],
[case "${enableval}" in
yes) vp8=true ;;
no) vp8=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --disable-vp8) ;;
esac],[vp8=true])
if test x$vp8 = xtrue; then
PKG_CHECK_MODULES(VP8, [vpx >= 0.9.6 ], [have_vp8=yes],
[have_vp8=no])
PKG_CHECK_MODULES(SWSCALE, [libswscale >= 0.7.0 ],[have_vp8=$have_vp8] , have_vp8=no)
fi
if test "$have_vp8" = "true" ; then
VIDEO_CFLAGS=" $VIDEO_CFLAGS $SWSCALE_CFLAGS"
VIDEO_LIBS=" $VIDEO_LIBS $SWSCALE_LIBS"
fi
if test "$ffmpeg" = "false"; then
FFMPEG_CFLAGS=" $FFMPEG_CFLAGS -DNO_FFMPEG"
fi
......
......@@ -546,6 +546,7 @@ AM_CONDITIONAL(BUILD_GSM, test x$build_gsm = xyes )
MS_CHECK_VIDEO
AM_CONDITIONAL(BUILD_VIDEO, test "$video" = "true")
AM_CONDITIONAL(BUILD_THEORA, test "$have_theora" = "yes")
AM_CONDITIONAL(BUILD_VP8, test "$have_vp8" = "yes")
AM_CONDITIONAL(BUILD_WIN32, test "$mingw_found" = "yes")
AM_CONDITIONAL(BUILD_WIN32_WCE, test "$mingw32ce_found" = "yes")
AM_CONDITIONAL(BUILD_FFMPEG, test "$ffmpeg" = "true")
......
......@@ -133,6 +133,10 @@ if BUILD_THEORA
libmediastreamer_la_SOURCES+=theora.c
endif
if BUILD_VP8
libmediastreamer_la_SOURCES+=vp8.c
endif
if BUILD_FFMPEG
libmediastreamer_la_SOURCES+= videoenc.c \
videodec.c \
......
......@@ -91,10 +91,6 @@ static void dec_snow_init(MSFilter *f){
dec_init(f,CODEC_ID_SNOW);
}
static void dec_vp8_init(MSFilter *f){
dec_init(f,CODEC_ID_VP8);
}
static void dec_uninit(MSFilter *f){
DecState *s=(DecState*)f->data;
if (s->av_context.codec!=NULL){
......@@ -220,24 +216,6 @@ static mblk_t * skip_rfc2429_header(mblk_t *inm){
return NULL;
}
static mblk_t * skip_draft_vp8_header(mblk_t *inm){
if (msgdsize(inm) >= 2){
unsigned char vp8_payload_desc = *(inm->b_rptr);
int payload_desc_size = 1;
/* has picture id ? */
if (vp8_payload_desc & 0x10) {
/* extended picture id ? */
if (inm->b_rptr[1] & 0x80)
payload_desc_size = 3;
else
payload_desc_size = 2;
}
inm->b_rptr += payload_desc_size;
return inm;
}else freemsg(inm);
return NULL;
}
static mblk_t * parse_snow_header(DecState *s,mblk_t *inm){
if (msgdsize(inm) >= 4){
uint32_t h = ntohl(*(uint32_t*)inm->b_rptr);
......@@ -660,7 +638,6 @@ static void dec_process_frame(MSFilter *f, mblk_t *inm){
else if (f->desc->id==MS_H263_OLD_DEC_ID) inm=skip_rfc2190_header(inm);
else if (s->codec==CODEC_ID_SNOW && s->input==NULL) inm=parse_snow_header(s,inm);
else if (s->codec==CODEC_ID_MJPEG && f->desc->id==MS_JPEG_DEC_ID) inm=read_rfc2435_header(s,inm);
else if (f->desc->id==MS_VP8_DEC_ID) inm=skip_draft_vp8_header(inm);
if (inm){
/* accumulate the video packet until we have the rtp markbit*/
......@@ -926,29 +903,12 @@ MSFilterDesc ms_snow_dec_desc={
.methods= methods
};
MSFilterDesc ms_vp8_dec_desc={
.id=MS_VP8_DEC_ID,
.name="MSVp8Dec",
.text="A VP8 decoder using ffmpeg library",
.category=MS_FILTER_DECODER,
.enc_fmt="VP8-DRAFT-0-3-2",
.ninputs=1,
.noutputs=1,
.init=dec_vp8_init,
.preprocess=dec_preprocess,
.process=dec_process,
.postprocess=dec_postprocess,
.uninit=dec_uninit,
.methods= methods
};
#endif
MS_FILTER_DESC_EXPORT(ms_mpeg4_dec_desc)
MS_FILTER_DESC_EXPORT(ms_h263_dec_desc)
MS_FILTER_DESC_EXPORT(ms_h263_old_dec_desc)
MS_FILTER_DESC_EXPORT(ms_snow_dec_desc)
MS_FILTER_DESC_EXPORT(ms_vp8_dec_desc)
/* decode JPEG image with RTP/jpeg headers */
MS_FILTER_DESC_EXPORT(ms_jpeg_dec_desc)
......
......@@ -1139,22 +1139,6 @@ MSFilterDesc ms_mjpeg_enc_desc={
.methods=methods
};
MSFilterDesc ms_vp8_enc_desc={
.id=MS_VP8_ENC_ID,
.name="MSVp8Enc",
.text=N_("A video VP8 encoder using ffmpeg library."),
.category=MS_FILTER_ENCODER,
.enc_fmt="VP8-DRAFT-0-3-2",
.ninputs=1, /*MS_YUV420P is assumed on this input */
.noutputs=1,
.init=enc_vp8_init,
.preprocess=enc_preprocess,
.process=enc_process,
.postprocess=enc_postprocess,
.uninit=enc_uninit,
.methods=methods
};
#endif
void __register_ffmpeg_encoders_if_possible(void){
......@@ -1171,9 +1155,5 @@ void __register_ffmpeg_encoders_if_possible(void){
{
ms_filter_register(&ms_mjpeg_enc_desc);
}
if (avcodec_find_encoder(CODEC_ID_VP8))
{
ms_filter_register(&ms_vp8_enc_desc);
}
}
#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/msticker.h"
#include "mediastreamer2/msvideo.h"
#define VPX_CODEC_DISABLE_COMPAT 1
#include <vpx/vpx_encoder.h>
#include <vpx/vp8cx.h>
#include <libswscale/swscale.h>
#define interface (vpx_codec_vp8_cx())
#define VP8_PAYLOAD_DESC_RSV_MASK 0xE0
#define VP8_PAYLOAD_DESC_I_MASK 0x10
#define VP8_PAYLOAD_DESC_N_MASK 0x08
#define VP8_PAYLOAD_DESC_FI_MASK 0x06
#define VP8_PAYLOAD_DESC_B_MASK 0x01
#define VP8_PAYLOAD_DESC_FI_BEG_PARTITION 0x02
#define VP8_PAYLOAD_DESC_FI_END_PARTITION 0x04
#define VP8_PAYLOAD_DESC_FI_MID_PARTITION 0x06
typedef struct EncState {
vpx_codec_ctx_t codec;
vpx_codec_enc_cfg_t cfg;
int width, height;
unsigned int mtu;
float fps;
} EncState;
static void vp8_fragment_and_send(MSFilter *f,EncState *s,mblk_t *frame, uint32_t timestamp, const vpx_codec_cx_pkt_t *pkt);
static void enc_init(MSFilter *f) {
vpx_codec_err_t res;
EncState *s=(EncState *)ms_new(EncState,1);
ms_message("Using %s\n",vpx_codec_iface_name(interface));
/* Populate encoder configuration */
res = vpx_codec_enc_config_default(interface, &s->cfg, 0);
if(res) {
ms_error("Failed to get config: %s\n", vpx_codec_err_to_string(res));
}
s->width = MS_VIDEO_SIZE_CIF_W;
s->height = MS_VIDEO_SIZE_CIF_H;
s->cfg.g_w = s->width;
s->cfg.g_h = s->height;
/* encoder automatically places keyframes */
s->cfg.kf_mode = VPX_KF_AUTO;
s->cfg.kf_min_dist = 0;
s->cfg.kf_max_dist = 150;
s->cfg.rc_target_bitrate = 250;
s->cfg.g_pass = VPX_RC_ONE_PASS;
s->fps=15;
s->cfg.g_timebase.num = 1;
s->cfg.g_timebase.den = s->fps;
s->cfg.rc_end_usage = VPX_VBR;
s->mtu=ms_get_payload_max_size()-1;/*-1 for the vp8 payload header*/
f->data = s;
}
static void enc_uninit(MSFilter *f) {
EncState *s=(EncState*)f->data;
vpx_codec_destroy(&s->codec);
ms_free(s);
}
static void enc_preprocess(MSFilter *f) {
EncState *s=(EncState*)f->data;
s->cfg.rc_target_bitrate = MS_VIDEO_SIZE_CIF_W * MS_VIDEO_SIZE_CIF_H * s->cfg.rc_target_bitrate
/ s->cfg.g_w / s->cfg.g_h;
s->cfg.g_w = s->width;
s->cfg.g_h = s->height;
/* Initialize codec */
vpx_codec_err_t res = vpx_codec_enc_init(&s->codec, interface, &s->cfg, 0);
if (res) {
ms_error("vpx_codec_enc_init failed: %s (%s)n", vpx_codec_err_to_string(res), vpx_codec_error_detail(&s->codec));
}
/* vpx_codec_control(&s->codec, VP8E_SET_CPUUSED, 0);*/ /* -16 (quality) .. 16 (speed) */
/* scaling test
vpx_scaling_mode_t scaling;
scaling.h_scaling_mode = VP8E_ONETWO;
scaling.v_scaling_mode = VP8E_ONETWO;
res = vpx_codec_control(&s->codec, VP8E_SET_SCALEMODE, &scaling);
if (res) {
ms_error("vpx_codec_control(VP8E_SET_SCALEMODE) failed: %s (%s)n", vpx_codec_err_to_string(res), vpx_codec_error_detail(&s->codec));
}
*/
}
static void enc_process(MSFilter *f) {
mblk_t *im,*om;
uint64_t timems=f->ticker->time;
uint32_t timestamp=timems*90;
EncState *s=(EncState*)f->data;
unsigned int flags = 0;
vpx_codec_err_t err;
while((im=ms_queue_get(f->inputs[0]))!=NULL){
om = NULL;
flags = 0;
vpx_image_t img;
vpx_img_wrap(&img, VPX_IMG_FMT_I420, s->width, s->height, 1, im->b_rptr);
err = vpx_codec_encode(&s->codec, &img, timems, 1, flags, 0);
if (err) {
ms_error("vpx_codec_encode failed : %d %s (%s)\n", err, vpx_codec_err_to_string(err), vpx_codec_error_detail(&s->codec));
} else {
vpx_codec_iter_t iter = NULL;
const vpx_codec_cx_pkt_t *pkt;
while( (pkt = vpx_codec_get_cx_data(&s->codec, &iter)) ) {
if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
om = allocb(pkt->data.frame.sz,0);
memcpy(om->b_wptr, pkt->data.frame.buf, pkt->data.frame.sz);
om->b_wptr += pkt->data.frame.sz;
vp8_fragment_and_send(f, s, om, timestamp, pkt);
}
}
}
freemsg(im);
}
}
static void enc_postprocess(MSFilter *f) {
}
static int enc_set_vsize(MSFilter *f, void*data){
MSVideoSize *vs=(MSVideoSize*)data;
EncState *s=(EncState*)f->data;
s->width=vs->width;
s->height=vs->height;
return 0;
}
static int enc_get_vsize(MSFilter *f, void *data){
EncState *s=(EncState*)f->data;
MSVideoSize *vs=(MSVideoSize*)data;
vs->width=s->width;
vs->height=s->height;
return 0;
}
static int enc_add_attr(MSFilter *f, void*data){
/*const char *attr=(const char*)data;
EncState *s=(EncState*)f->data;*/
return 0;
}
static int enc_set_fps(MSFilter *f, void *data){
float *fps=(float*)data;
EncState *s=(EncState*)f->data;
s->fps=*fps;
return 0;
}
static int enc_get_fps(MSFilter *f, void *data){
EncState *s=(EncState*)f->data;
float *fps=(float*)data;
*fps=s->fps;
return 0;
}
static int enc_set_br(MSFilter *f, void*data){
int br=*(int*)data;
EncState *s=(EncState*)f->data;
s->cfg.rc_target_bitrate = br / 1024;
return 0;
}
static int enc_set_mtu(MSFilter *f, void*data){
EncState *s=(EncState*)f->data;
s->mtu=*(int*)data;
return 0;
}
static MSFilterMethod enc_methods[]={
{ MS_FILTER_SET_VIDEO_SIZE, enc_set_vsize },
{ MS_FILTER_SET_FPS, enc_set_fps },
{ MS_FILTER_GET_VIDEO_SIZE, enc_get_vsize },
{ MS_FILTER_GET_FPS, enc_get_fps },
{ MS_FILTER_ADD_ATTR, enc_add_attr },
{ MS_FILTER_SET_BITRATE, enc_set_br },
{ MS_FILTER_SET_MTU, enc_set_mtu },
{ 0 , NULL }
};
MSFilterDesc ms_vp8_enc_desc={
.id=MS_VP8_ENC_ID,
.name="MSVp8Enc",
.text=N_("A video VP8 encoder using libvpx library."),
.category=MS_FILTER_ENCODER,
.enc_fmt="VP8-DRAFT-0-3-2",
.ninputs=1, /*MS_YUV420P is assumed on this input */
.noutputs=1,
.init=enc_init,
.preprocess=enc_preprocess,
.process=enc_process,
.postprocess=enc_postprocess,
.uninit=enc_uninit,
.methods=enc_methods
};
MS_FILTER_DESC_EXPORT(ms_vp8_enc_desc)
static void vp8_fragment_and_send(MSFilter *f,EncState *s,mblk_t *frame, uint32_t timestamp, const vpx_codec_cx_pkt_t *pkt){
uint8_t *rptr;
mblk_t *packet=NULL;
mblk_t* vp8_payload_desc = NULL;
int len;
bool_t fragmented = (frame->b_wptr-frame->b_rptr > s->mtu);
#if 0
if ((pkt->data.frame.flags & VPX_FRAME_IS_KEY) == 0) {
ms_debug("P-FRAME: %u\n", pkt->data.frame.sz);
} else {
ms_debug("I-FRAME: %u\n", pkt->data.frame.sz);
}
#endif
for (rptr=frame->b_rptr;rptr<frame->b_wptr;){
vp8_payload_desc = allocb(1, 0);
vp8_payload_desc->b_wptr=vp8_payload_desc->b_rptr+1;
len=MIN(s->mtu,(frame->b_wptr-rptr));
packet=dupb(frame);
packet->b_rptr=rptr;
packet->b_wptr=rptr+len;
mblk_set_timestamp_info(packet,timestamp);
mblk_set_timestamp_info(vp8_payload_desc,timestamp);
/* insert 1 byte vp8 payload descriptor */
(*vp8_payload_desc->b_rptr) = 0;
/* RSV field, always 0 */
(*vp8_payload_desc->b_rptr) &= ~VP8_PAYLOAD_DESC_RSV_MASK;
/* I (1 if picture ID is present) */
/* (*vp8_payload_desc->b_rptr) |= 0x10; */
/* N : set to 1 if non reference frame */
if ((pkt->data.frame.flags & VPX_FRAME_IS_KEY) == 0)
(*vp8_payload_desc->b_rptr) |= VP8_PAYLOAD_DESC_I_MASK;
/* FI : partition fragmentation information */
/* (currently frame frag) */
if (fragmented) {
if (rptr == frame->b_rptr) {
/* first fragment */
(*vp8_payload_desc->b_rptr) |= VP8_PAYLOAD_DESC_FI_BEG_PARTITION;
} else if ((rptr+len)>=frame->b_wptr) {
/* last one */
(*vp8_payload_desc->b_rptr) |= VP8_PAYLOAD_DESC_FI_END_PARTITION;
} else {
(*vp8_payload_desc->b_rptr) |= VP8_PAYLOAD_DESC_FI_MID_PARTITION;
}
}
/* B : frame beginning */
if (rptr == frame->b_rptr) {
(*vp8_payload_desc->b_rptr) |= VP8_PAYLOAD_DESC_B_MASK;
}
vp8_payload_desc->b_cont = packet;
ms_queue_put(f->outputs[0], vp8_payload_desc);
rptr+=len;
}
freeb(frame);
/*set marker bit on last packet*/
mblk_set_marker_info(packet,TRUE);
mblk_set_marker_info(vp8_payload_desc,TRUE);
}
#undef interface
#include <assert.h>
#include <vpx/vpx_decoder.h>
#include <vpx/vp8dx.h>
#define interface (vpx_codec_vp8_dx())
typedef struct DecState {
vpx_codec_ctx_t codec;
mblk_t *curframe;
struct SwsContext * scale_ctx;
int scale_from_w, scale_from_h;
} DecState;
static void dec_init(MSFilter *f) {
DecState *s=(DecState *)ms_new(DecState,1);
ms_message("Using %s\n",vpx_codec_iface_name(interface));
/* Initialize codec */
if(vpx_codec_dec_init(&s->codec, interface, NULL, 0))
ms_error("Failed to initialize decoder");
s->curframe = NULL;
s->scale_ctx = NULL;
s->scale_from_w = s->scale_from_h = 0;
f->data = s;
}
static void dec_preprocess(MSFilter* f) {
}
static void dec_uninit(MSFilter *f) {
DecState *s=(DecState*)f->data;
vpx_codec_destroy(&s->codec);
if (s->curframe!=NULL)
freemsg(s->curframe);
if (s->scale_ctx != NULL)
sws_freeContext(s->scale_ctx);
ms_free(s);
}
/* remove payload header and agregates fragmented packets */
static mblk_t *dec_unpacketize(MSFilter *f, DecState *s, mblk_t *im, bool_t* is_key_frame){
uint8_t vp8_payload_desc = *im->b_rptr++;
(*is_key_frame) = !(vp8_payload_desc & VP8_PAYLOAD_DESC_I_MASK);
if ((vp8_payload_desc & VP8_PAYLOAD_DESC_FI_MASK) == 0) {
return im;
}
if ((vp8_payload_desc & VP8_PAYLOAD_DESC_FI_MASK)
== VP8_PAYLOAD_DESC_FI_BEG_PARTITION) {
if (s->curframe!=NULL)
freemsg(s->curframe);
s->curframe=im;
} else if ((vp8_payload_desc & VP8_PAYLOAD_DESC_FI_MASK)
== VP8_PAYLOAD_DESC_FI_MID_PARTITION) {
if (s->curframe!=NULL)
concatb(s->curframe,im);
else
freemsg(im);
} else {
assert((vp8_payload_desc & VP8_PAYLOAD_DESC_FI_MASK)
== VP8_PAYLOAD_DESC_FI_END_PARTITION);
if (s->curframe!=NULL){
mblk_t *ret;
concatb(s->curframe,im);
msgpullup(s->curframe,-1);
ret=s->curframe;
s->curframe=NULL;
return ret;
}else
freemsg(im);
}
return NULL;
}
static unsigned int compute_scaled_size(int size, VPX_SCALING_MODE scale) {
switch (scale) {
case VP8E_FOURFIVE:
return (size * 4) / 5;
case VP8E_THREEFIVE:
return (size * 3) / 5;
case VP8E_ONETWO:
return (size * 1) / 2;
case VP8E_NORMAL:
default:
return size;
}
}
static bool_t check_swscale_init(DecState *s, mblk_t *m, int w, int h, bool_t is_key_frame) {
unsigned char h_scaling_mode, v_scaling_mode;
unsigned int input_w, input_h;
if (!is_key_frame) {
return (s->scale_ctx != NULL);
}
/* see decodframe.c or vp8 bitstream guide */
h_scaling_mode = m->b_rptr[3+4] >> 6;
v_scaling_mode = m->b_rptr[3+6] >> 6;
input_w = compute_scaled_size(w, h_scaling_mode);
input_h = compute_scaled_size(h, v_scaling_mode);
/* check if sws_context is properly initialized */
if (s->scale_from_w != input_w || s->scale_from_h != input_h) {
/* we need a key frame */
if (!is_key_frame) {
freemsg(m);
return FALSE;
}
if (s->scale_ctx != NULL)
sws_freeContext(s->scale_ctx);
s->scale_ctx = sws_getContext(
input_w, input_h, PIX_FMT_YUV420P,
MS_VIDEO_SIZE_CIF_W, MS_VIDEO_SIZE_CIF_H, PIX_FMT_YUV420P,
SWS_FAST_BILINEAR|SWS_CPU_CAPS_MMX2,
NULL, NULL, NULL);
s->scale_from_w = input_w;
s->scale_from_h = input_h;
}
return TRUE;
}
static void dec_process(MSFilter *f) {
mblk_t *im;
mblk_t *m;
vpx_codec_err_t err;
DecState *s=(DecState*)f->data;
bool_t is_key_frame = FALSE;
const int size = (MS_VIDEO_SIZE_CIF_W * MS_VIDEO_SIZE_CIF_H * 3) / 2;
while( (im=ms_queue_get(f->inputs[0]))!=0) {
m = dec_unpacketize(f, s, im, &is_key_frame);
if (m!=NULL){
vpx_codec_iter_t iter = NULL;
vpx_image_t *img;
err = vpx_codec_decode(&s->codec, m->b_rptr, m->b_wptr - m->b_rptr, NULL, 0);
if (err) {
ms_warning("vpx_codec_decode failed : %d %s (%s)\n", err, vpx_codec_err_to_string(err), vpx_codec_error_detail(&s->codec));
}
/* browse decoded frame */
while((img = vpx_codec_get_frame(&s->codec, &iter))) {
mblk_t* om;
vpx_image_t out_img;
if (!check_swscale_init(s, m, img->d_w, img->d_h, is_key_frame)) {
continue;
}
/* scale/copy frame to destination mblk_t */
om = allocb(size, 0);
vpx_img_wrap(&out_img, VPX_IMG_FMT_I420, MS_VIDEO_SIZE_CIF_W, MS_VIDEO_SIZE_CIF_H, 1, om->b_wptr);
assert(sws_scale(s->scale_ctx, img->planes, img->stride, 0, MS_VIDEO_SIZE_CIF_H, out_img.planes, out_img.stride) == MS_VIDEO_SIZE_CIF_H);
om->b_wptr += size;
ms_queue_put(f->outputs[0],om);
}
freemsg(m);
}
}
}
MSFilterDesc ms_vp8_dec_desc={
.id=MS_VP8_DEC_ID,
.name="MSVp8Dec",
.text="A VP8 decoder using libvpx library",
.category=MS_FILTER_DECODER,
.enc_fmt="VP8-DRAFT-0-3-2",
.ninputs=1,
.noutputs=1,
.init=dec_init,
.preprocess=dec_preprocess,
.process=dec_process,
.postprocess=NULL,
.uninit=dec_uninit,
.methods=NULL
};
MS_FILTER_DESC_EXPORT(ms_vp8_dec_desc)
......@@ -30,6 +30,7 @@ LDADD= $(top_builddir)/src/libmediastreamer.la \
$(SPEEX_LIBS) \
$(GSM_LIBS) \
$(THEORA_LIBS) \