FS-7500: Work in progress. Added codec config params that can be set from session and made vpx codec re-init on size change. Also add periodic key frame timer

This commit is contained in:
Anthony Minessale
2014-11-14 19:01:56 -06:00
committed by Michael Jerris
parent 365a5dd820
commit 659c1e474e
10 changed files with 536 additions and 374 deletions
+102 -78
View File
@@ -44,18 +44,21 @@
SWITCH_MODULE_LOAD_FUNCTION(mod_vpx_load);
SWITCH_MODULE_DEFINITION(mod_vpx, mod_vpx_load, NULL, NULL);
#define encoder_interface (vpx_codec_vp8_cx())
#define decoder_interface (vpx_codec_vp8_dx())
struct vpx_context {
switch_codec_t *codec;
unsigned int flags;
switch_codec_settings_t codec_settings;
unsigned int bandwidth;
vpx_codec_enc_cfg_t config;
vpx_codec_ctx_t encoder;
uint8_t encoder_init;
vpx_image_t *pic;
switch_bool_t force_key_frame;
int width;
int height;
int bitrate;
int fps;
int format;
int intra_period;
@@ -65,9 +68,8 @@ struct vpx_context {
const vpx_codec_cx_pkt_t *pkt;
int pkt_pos;
vpx_codec_iter_t iter;
switch_time_t last_ts;
vpx_codec_ctx_t decoder;
uint8_t decoder_init;
switch_buffer_t *vpx_packet_buffer;
int got_key_frame;
switch_size_t last_received_timestamp;
@@ -76,48 +78,17 @@ struct vpx_context {
};
typedef struct vpx_context vpx_context_t;
static switch_status_t switch_vpx_init(switch_codec_t *codec, switch_codec_flag_t flags, const switch_codec_settings_t *codec_settings)
static switch_status_t init_codec(switch_codec_t *codec)
{
vpx_context_t *context = NULL;
int encoding, decoding;
vpx_codec_ctx_t *encoder = NULL;
vpx_codec_ctx_t *decoder = NULL;
vpx_codec_enc_cfg_t *config;
const vpx_codec_iface_t* encoder_interface = vpx_codec_vp8_cx();
const vpx_codec_iface_t* decoder_interface = vpx_codec_vp8_dx();
encoding = (flags & SWITCH_CODEC_FLAG_ENCODE);
decoding = (flags & SWITCH_CODEC_FLAG_DECODE);
if (!(encoding || decoding) || ((context = switch_core_alloc(codec->memory_pool, sizeof(*context))) == 0)) {
return SWITCH_STATUS_FALSE;
}
memset(context, 0, sizeof(*context));
context->flags = flags;
codec->private_info = context;
if (codec->fmtp_in) {
codec->fmtp_out = switch_core_strdup(codec->memory_pool, codec->fmtp_in);
}
config = &context->config;
if (vpx_codec_enc_config_default(encoder_interface, config, 0) != VPX_CODEC_OK) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Encoder config Error\n");
return SWITCH_STATUS_FALSE;
}
// very big defaults till we know why scaling segs it
context->width = 3840;
context->height = 2160;
context->bitrate = 3840000;
vpx_context_t *context = (vpx_context_t *)codec->private_info;
vpx_codec_enc_cfg_t *config = &context->config;
// settings
config->g_profile = 1;
config->g_w = context->width;
config->g_h = context->height;
config->rc_target_bitrate = context->bitrate;
config->g_w = context->codec_settings.video.width;
config->g_h = context->codec_settings.video.height;
config->rc_target_bitrate = context->bandwidth;
config->g_timebase.num = 1;
config->g_timebase.den = 1000;
config->g_error_resilient = VPX_ERROR_RESILIENT_PARTITIONS;
@@ -127,11 +98,11 @@ static switch_status_t switch_vpx_init(switch_codec_t *codec, switch_codec_flag_
config->rc_dropframe_thresh = 0;
config->rc_end_usage = VPX_CBR;
config->g_pass = VPX_RC_ONE_PASS;
// config->kf_mode = VPX_KF_DISABLED;
config->kf_mode = VPX_KF_AUTO;
// config->kf_min_dist = FPS;// Intra Period 3 seconds;
// config->kf_max_dist = FPS * 3;
config->rc_resize_allowed = 0;
config->kf_mode = VPX_KF_DISABLED;
//config->kf_mode = VPX_KF_AUTO;
//config->kf_min_dist = FPS;// Intra Period 3 seconds;
//config->kf_max_dist = FPS;
config->rc_resize_allowed = 1;
config->rc_min_quantizer = 2;
config->rc_max_quantizer = 56;
//Rate control adaptation undershoot control.
@@ -157,59 +128,74 @@ static switch_status_t switch_vpx_init(switch_codec_t *codec, switch_codec_flag_
// indicates that the client will buffer (at least) 5000ms worth
// of encoded data. Use the target bitrate (rc_target_bitrate) to
// convert to bits/bytes, if necessary.
config->rc_buf_sz = 1000;
config->rc_buf_sz = 5000;
//Decoder Buffer Initial Size.
// This value indicates the amount of data that will be buffered
// by the decoding application prior to beginning playback.
// This value is expressed in units of time (milliseconds).
// Use the target bitrate (rc_target_bitrate) to convert to
// bits/bytes, if necessary.
config->rc_buf_initial_sz = 500;
config->rc_buf_initial_sz = 1000;
//Decoder Buffer Optimal Size.
// This value indicates the amount of data that the encoder should
// try to maintain in the decoder's buffer. This value is expressed
// in units of time (milliseconds).
// Use the target bitrate (rc_target_bitrate) to convert to
// bits/bytes, if necessary.
config->rc_buf_optimal_sz = 600;
config->rc_buf_optimal_sz = 1000;
if (context->flags & SWITCH_CODEC_FLAG_ENCODE) {
if (context->encoder_init) {
vpx_codec_destroy(&context->encoder);
context->encoder_init = 0;
}
if (encoding) {
if (vpx_codec_enc_init(&context->encoder, encoder_interface, config, 0 & VPX_CODEC_USE_OUTPUT_PARTITION) != VPX_CODEC_OK) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Codec init error: [%d:%s]\n", encoder->err, encoder->err_detail);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Codec init error: [%d:%s]\n", context->encoder.err, context->encoder.err_detail);
return SWITCH_STATUS_FALSE;
}
context->encoder_init = 1;
// The static threshold imposes a change threshold on blocks below which they will be skipped by the encoder.
vpx_codec_control(encoder, VP8E_SET_STATIC_THRESHOLD, 100);
vpx_codec_control(&context->encoder, VP8E_SET_STATIC_THRESHOLD, 100);
//Set cpu usage, a bit lower than normal (-6) but higher than android (-12)
vpx_codec_control(encoder, VP8E_SET_CPUUSED, -8);
vpx_codec_control(&context->encoder, VP8E_SET_CPUUSED, -6);
// Only one partition
// vpx_codec_control(encoder, VP8E_SET_TOKEN_PARTITIONS, VP8_ONE_TOKENPARTITION);
// vpx_codec_control(&context->encoder, VP8E_SET_TOKEN_PARTITIONS, VP8_ONE_TOKENPARTITION);
// Enable noise reduction
vpx_codec_control(encoder, VP8E_SET_NOISE_SENSITIVITY, 0);
vpx_codec_control(&context->encoder, VP8E_SET_NOISE_SENSITIVITY, 1);
//Set max data rate for Intra frames.
// This value controls additional clamping on the maximum size of a keyframe.
// It is expressed as a percentage of the average per-frame bitrate, with the
// special (and default) value 0 meaning unlimited, or no additional clamping
// beyond the codec's built-in algorithm.
// For example, to allocate no more than 4.5 frames worth of bitrate to a keyframe, set this to 450.
vpx_codec_control(encoder, VP8E_SET_MAX_INTRA_BITRATE_PCT, 0);
vpx_codec_control(&context->encoder, VP8E_SET_MAX_INTRA_BITRATE_PCT, 0);
}
if (decoding) {
if (context->flags & SWITCH_CODEC_FLAG_DECODE) {
vp8_postproc_cfg_t ppcfg;
if (context->decoder_init) {
vpx_codec_destroy(&context->decoder);
context->decoder_init = 0;
}
if (vpx_codec_dec_init(&context->decoder, decoder_interface, NULL, VPX_CODEC_USE_POSTPROC) != VPX_CODEC_OK) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Codec init error: [%d:%s]\n", encoder->err, encoder->err_detail);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Codec init error: [%d:%s]\n", context->encoder.err, context->encoder.err_detail);
return SWITCH_STATUS_FALSE;
}
context->decoder_init = 1;
// the types of post processing to be done, should be combination of "vp8_postproc_level"
ppcfg.post_proc_flag = VP8_DEMACROBLOCK | VP8_DEBLOCK;
// the strength of deblocking, valid range [0, 16]
ppcfg.deblocking_level = 3;
// Set deblocking settings
vpx_codec_control(decoder, VP8_SET_POSTPROC, &ppcfg);
vpx_codec_control(&context->decoder, VP8_SET_POSTPROC, &ppcfg);
switch_buffer_create_dynamic(&context->vpx_packet_buffer, 512, 512, 1024000);
}
@@ -217,6 +203,39 @@ static switch_status_t switch_vpx_init(switch_codec_t *codec, switch_codec_flag_
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t switch_vpx_init(switch_codec_t *codec, switch_codec_flag_t flags, const switch_codec_settings_t *codec_settings)
{
vpx_context_t *context = NULL;
int encoding, decoding;
encoding = (flags & SWITCH_CODEC_FLAG_ENCODE);
decoding = (flags & SWITCH_CODEC_FLAG_DECODE);
if (!(encoding || decoding) || ((context = switch_core_alloc(codec->memory_pool, sizeof(*context))) == 0)) {
return SWITCH_STATUS_FALSE;
}
memset(context, 0, sizeof(*context));
context->flags = flags;
codec->private_info = context;
if (codec_settings) {
context->codec_settings = *codec_settings;
}
if (codec->fmtp_in) {
codec->fmtp_out = switch_core_strdup(codec->memory_pool, codec->fmtp_in);
}
if (vpx_codec_enc_config_default(encoder_interface, &context->config, 0) != VPX_CODEC_OK) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Encoder config Error\n");
return SWITCH_STATUS_FALSE;
}
return SWITCH_STATUS_SUCCESS;
}
/* http://tools.ietf.org/html/draft-ietf-payload-vp8-10
The first octets after the RTP header are the VP8 payload descriptor, with the following structure.
@@ -313,8 +332,8 @@ static switch_status_t consume_partition(vpx_context_t *context, void *data, uin
}
static switch_status_t switch_vpx_encode(switch_codec_t *codec, switch_image_t *img,
void *encoded_data, uint32_t *encoded_data_len,
unsigned int *flag)
void *encoded_data, uint32_t *encoded_data_len,
unsigned int *flag)
{
vpx_context_t *context = (vpx_context_t *)codec->private_info;
uint32_t duration = 90000 / FPS;
@@ -332,6 +351,8 @@ static switch_status_t switch_vpx_encode(switch_codec_t *codec, switch_image_t *
//d_w and d_h are messed up
//printf("WTF %d %d\n", img->d_w, img->d_h);
width = img->w;
height = img->h;
@@ -339,28 +360,31 @@ static switch_status_t switch_vpx_encode(switch_codec_t *codec, switch_image_t *
//switch_assert(height > 0 && (height % 4 == 0));
if (context->config.g_w != width || context->config.g_h != height) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "VPX reset encoder picture from %dx%d to %dx%d\n", context->config.g_w, context->config.g_h, width, height);
context->config.g_w = width;
context->config.g_h = height;
if (vpx_codec_enc_config_set(&context->encoder, &context->config) != VPX_CODEC_OK) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "VPX reset config error!");
context->codec_settings.video.width = width;
context->codec_settings.video.height = height;
if (context->codec_settings.video.bandwidth) {
context->bandwidth = context->codec_settings.video.bandwidth;
} else {
context->bandwidth = width * height * 8;
}
}
if (context->last_ts == 0) context->last_ts = switch_micro_time_now();
if (context->bandwidth > 1250000) {
context->bandwidth = 1250000;
}
if ((switch_micro_time_now() - context->last_ts) > 2 * 1000000) {
// the config params doesn't seems work for generate regular key frames,
// so we do some trick here to force a key frame every 2 sec
// vpx_flags = VPX_EFLAG_FORCE_KF;
context->last_ts = switch_micro_time_now();
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(codec->session), SWITCH_LOG_NOTICE,
"VPX reset encoder picture from %dx%d to %dx%d %u BW\n",
context->config.g_w, context->config.g_h, width, height, context->bandwidth);
init_codec(codec);
*flag |= SFF_PICTURE_RESET;
context->need_key_frame = 1;
}
if (context->need_key_frame > 0) {
// force generate a key frame
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "VPX KEYFRAME REQ\n");
vpx_flags |= VPX_EFLAG_FORCE_KF;
context->last_ts = switch_micro_time_now();
context->need_key_frame--;
}
@@ -539,7 +563,7 @@ static switch_status_t switch_vpx_destroy(switch_codec_t *codec)
if (context) {
if ((codec->flags & SWITCH_CODEC_FLAG_ENCODE)) {
vpx_codec_destroy(&context->encoder); // TODO fix crash
vpx_codec_destroy(&context->encoder);
}
if ((codec->flags & SWITCH_CODEC_FLAG_DECODE)) {
+47 -58
View File
@@ -102,6 +102,7 @@ struct vlc_video_context {
switch_mutex_t *video_mutex;
switch_core_session_t *session;
switch_channel_t *channel;
switch_frame_t *aud_frame;
switch_frame_t *vid_frame;
uint8_t video_packet[1500 + 12];
@@ -230,20 +231,11 @@ static void *vlc_video_lock_callback(void *data, void **p_pixels)
return NULL; /* picture identifier, not needed here */
}
/* dummy callback so it should be good when no video on channel */
static void vlc_video_unlock_dummy_callback(void *data, void *id, void *const *p_pixels)
{
vlc_video_context_t *context = (vlc_video_context_t *)data;
assert(id == NULL); /* picture identifier, not needed here */
switch_mutex_unlock(context->video_mutex);
}
static void vlc_video_unlock_callback(void *data, void *id, void *const *p_pixels)
{
vlc_video_context_t *context = (vlc_video_context_t *) data;
switch_frame_t *frame = context->vid_frame;
switch_assert(id == NULL); /* picture identifier, not needed here */
if (context->channel && !switch_channel_test_flag(context->channel, CF_VIDEO)) return;
if (!context->img) context->img = switch_img_alloc(NULL, SWITCH_IMG_FMT_I420, context->width, context->height, 0);
@@ -251,14 +243,11 @@ static void vlc_video_unlock_callback(void *data, void *id, void *const *p_pixel
yuyv_to_i420(*p_pixels, context->img->img_data, context->width, context->height);
switch_core_session_write_video_image(context->session, frame, context->img, SWITCH_DEFAULT_VIDEO_SIZE, NULL);
switch_mutex_unlock(context->video_mutex);
}
static void do_buffer_frame(vlc_video_context_t *context)
static void do_buffer_frame(vlc_video_context_t *context, switch_frame_t *frame)
{
switch_frame_t *frame = context->vid_frame;
uint32_t size = sizeof(*frame) + frame->packetlen;
switch_mutex_lock(context->video_mutex);
@@ -277,32 +266,35 @@ static void do_buffer_frame(vlc_video_context_t *context)
static void vlc_video_channel_unlock_callback(void *data, void *id, void *const *p_pixels)
{
vlc_video_context_t *context = (vlc_video_context_t *)data;
uint32_t flag = 0;
switch_frame_t *frame = context->vid_frame;
switch_assert(id == NULL); /* picture identifier, not needed here */
if (context->channel && !switch_channel_test_flag(context->channel, CF_VIDEO)) return;
if (!context->img) context->img = switch_img_alloc(NULL, SWITCH_IMG_FMT_I420, context->width, context->height, 0);
switch_assert(context->img);
yuyv_to_i420(*p_pixels, context->img->img_data, context->width, context->height);
switch_mutex_unlock(context->video_mutex);
}
static void vlc_video_display_callback(void *data, void *id)
{
vlc_video_context_t *context = (vlc_video_context_t *) data;
int32_t flag = 0;
/* VLC wants to display the video */
if (context->channel && !switch_channel_test_flag(context->channel, CF_VIDEO)) return;
if (context->video_refresh_req > 0) {
flag |= SFF_WAIT_KEY_FRAME;
context->video_refresh_req--;
}
switch_core_session_write_video_image(context->session, frame, context->img, SWITCH_DEFAULT_VIDEO_SIZE, &flag);
switch_mutex_unlock(context->video_mutex);
}
static void vlc_video_display_callback(void *data, void *id)
{
/* VLC wants to display the video */
(void) data;
assert(id == NULL);
switch_core_session_write_video_image(context->session, context->vid_frame, context->img, SWITCH_DEFAULT_VIDEO_SIZE, NULL);
}
unsigned video_format_setup_callback(void **opaque, char *chroma, unsigned *width, unsigned *height, unsigned *pitches, unsigned *lines)
@@ -723,7 +715,9 @@ SWITCH_STANDARD_APP(play_video_function)
audio_datalen = read_impl.decoded_bytes_per_packet; //codec.implementation->actual_samples_per_second / 1000 * (read_impl.microseconds_per_packet / 1000);
context->session = session;
context->channel = channel;
context->pool = pool;
context->aud_frame = &audio_frame;
context->vid_frame = &video_frame;
@@ -770,14 +764,8 @@ SWITCH_STANDARD_APP(play_video_function)
libvlc_audio_set_format(context->mp, "S16N", read_impl.actual_samples_per_second, read_impl.number_of_channels);
libvlc_audio_set_callbacks(context->mp, vlc_play_audio_callback, NULL,NULL,NULL,NULL, (void *) context);
if (switch_channel_test_flag(channel, CF_VIDEO)) {
// libvlc_video_set_format(context->mp, "YUYV", VIDEOWIDTH, VIDEOHEIGHT, VIDEOWIDTH * 2);
libvlc_video_set_format_callbacks(context->mp, video_format_setup_callback, video_format_clean_callback);
libvlc_video_set_callbacks(context->mp, vlc_video_lock_callback, vlc_video_unlock_callback, vlc_video_display_callback, context);
} else {
libvlc_video_set_format_callbacks(context->mp, video_format_setup_callback, video_format_clean_callback);
libvlc_video_set_callbacks(context->mp, vlc_video_lock_callback, vlc_video_unlock_dummy_callback, vlc_video_display_callback, context);
}
libvlc_video_set_format_callbacks(context->mp, video_format_setup_callback, video_format_clean_callback);
libvlc_video_set_callbacks(context->mp, vlc_video_lock_callback, vlc_video_unlock_callback, vlc_video_display_callback, context);
// start play
if (-1 == libvlc_media_player_play(context->mp)) {
@@ -928,6 +916,15 @@ switch_io_routines_t vlc_io_routines = {
/*state_run*/ NULL
};
static switch_status_t vlc_channel_img_callback(switch_core_session_t *session, switch_frame_t *frame, switch_image_t *img, void *user_data)
{
vlc_video_context_t *context = (vlc_video_context_t *) user_data;
do_buffer_frame(context, frame);
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t setup_tech_pvt(switch_core_session_t *session, const char *path)
{
switch_channel_t *channel = switch_core_session_get_channel(session);
@@ -952,11 +949,12 @@ static switch_status_t setup_tech_pvt(switch_core_session_t *session, const char
memset(context, 0, sizeof(vlc_file_context_t));
tech_pvt->context = context;
switch_buffer_create_dynamic(&(context->audio_buffer), VLC_BUFFER_SIZE, VLC_BUFFER_SIZE * 8, 0);
if (switch_channel_test_flag(channel, CF_VIDEO)) {
switch_buffer_create_dynamic(&(context->video_buffer), VLC_BUFFER_SIZE * 2, VLC_BUFFER_SIZE * 16, 0);
}
switch_buffer_create_dynamic(&(context->video_buffer), VLC_BUFFER_SIZE * 2, VLC_BUFFER_SIZE * 16, 0);
switch_core_session_set_image_write_callback(session, vlc_channel_img_callback, tech_pvt->context);
if (switch_core_timer_init(&tech_pvt->timer, "soft", 20,
8000 / (1000 / 20), pool) != SWITCH_STATUS_SUCCESS) {
@@ -969,6 +967,9 @@ static switch_status_t setup_tech_pvt(switch_core_session_t *session, const char
context->pool = pool;
context->aud_frame = &tech_pvt->read_frame;
context->vid_frame = &tech_pvt->read_video_frame;
context->vid_frame->packet = context->video_packet;
context->vid_frame->data = context->video_packet + 12;
context->playing = 0;
// context->err = 0;
@@ -1010,14 +1011,10 @@ static switch_status_t setup_tech_pvt(switch_core_session_t *session, const char
libvlc_audio_set_format(context->mp, "S16N", 8000, 1);
libvlc_audio_set_callbacks(context->mp, vlc_play_audio_callback, NULL,NULL,NULL,NULL, (void *) context);
if (switch_channel_test_flag(channel, CF_VIDEO)) {
// libvlc_video_set_format(context->mp, "YUYV", VIDEOWIDTH, VIDEOHEIGHT, VIDEOWIDTH * 2);
libvlc_video_set_format_callbacks(context->mp, video_format_setup_callback, video_format_clean_callback);
libvlc_video_set_callbacks(context->mp, vlc_video_lock_callback, vlc_video_channel_unlock_callback, vlc_video_display_callback, context);
} else {
libvlc_video_set_format_callbacks(context->mp, video_format_setup_callback, video_format_clean_callback);
libvlc_video_set_callbacks(context->mp, vlc_video_lock_callback, vlc_video_unlock_dummy_callback, vlc_video_display_callback, context);
}
libvlc_video_set_format_callbacks(context->mp, video_format_setup_callback, video_format_clean_callback);
libvlc_video_set_callbacks(context->mp, vlc_video_lock_callback, vlc_video_channel_unlock_callback, vlc_video_display_callback, context);
return SWITCH_STATUS_SUCCESS;
@@ -1026,25 +1023,13 @@ fail:
return status;
}
static switch_status_t vlc_channel_img_callback(switch_core_session_t *session, switch_frame_t *frame, switch_image_t *img, void *user_data)
{
vlc_video_context_t *context = (vlc_video_context_t *) user_data;
do_buffer_frame(context);
return SWITCH_STATUS_SUCCESS;
}
static switch_status_t channel_on_init(switch_core_session_t *session)
{
switch_channel_t *channel = switch_core_session_get_channel(session);
vlc_private_t *tech_pvt = switch_core_session_get_private(session);
//vlc_private_t *tech_pvt = switch_core_session_get_private(session);
switch_channel_set_state(channel, CS_CONSUME_MEDIA);
switch_core_session_set_image_write_callback(session, vlc_channel_img_callback, tech_pvt->context);
return SWITCH_STATUS_SUCCESS;
}
@@ -1096,6 +1081,10 @@ static switch_status_t channel_on_destroy(switch_core_session_t *session)
switch_buffer_destroy(&tech_pvt->context->audio_buffer);
}
if (tech_pvt->context->video_buffer) {
switch_buffer_destroy(&tech_pvt->context->video_buffer);
}
if (tech_pvt->timer.interval) {
switch_core_timer_destroy(&tech_pvt->timer);
}