diff --git a/lib/raop.h b/lib/raop.h index 322fe18..50c9f71 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -90,7 +90,7 @@ struct raop_callbacks_s { void (*register_client) (void *cls, const char *device_id, const char *pk_str, const char *name); bool (*check_register) (void *cls, const char *pk_str); void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id); - void (*video_set_codec)(void *cls, video_codec_t codec); + int (*video_set_codec)(void *cls, video_codec_t codec); /* for HLS video player controls */ void (*on_video_play) (void *cls, const char *location, const float start_position); void (*on_video_scrub) (void *cls, const float position); diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c index 149eff3..cd38896 100644 --- a/lib/raop_rtp_mirror.c +++ b/lib/raop_rtp_mirror.c @@ -196,7 +196,7 @@ raop_rtp_mirror_thread(void *arg) unsigned char nal_start_code[4] = { 0x00, 0x00, 0x00, 0x01 }; bool logger_debug = (logger_get_level(raop_rtp_mirror->logger) >= LOGGER_DEBUG); bool h265_video = false; - video_codec_t codec; + video_codec_t codec = VIDEO_CODEC_UNKNOWN; const char h264[] = "h264"; const char h265[] = "h265"; bool unsupported_codec = false; @@ -578,7 +578,6 @@ raop_rtp_mirror_thread(void *arg) video_stream_suspended = false; } - codec = VIDEO_CODEC_UNKNOWN; assert (raop_rtp_mirror->callbacks.video_set_codec); ntp_timestamp_nal = ntp_timestamp_raw; @@ -619,9 +618,21 @@ raop_rtp_mirror_thread(void *arg) if (!memcmp(payload + 4, hvc1, 4)) { /* hvc1 HECV detected */ - codec = VIDEO_CODEC_H265; - h265_video = true; - raop_rtp_mirror->callbacks.video_set_codec(raop_rtp_mirror->callbacks.cls, codec); + if (codec == VIDEO_CODEC_UNKNOWN) { + codec = VIDEO_CODEC_H265; + h265_video = true; + if (raop_rtp_mirror->callbacks.video_set_codec(raop_rtp_mirror->callbacks.cls, codec) < 0) { + logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "failed to set video codec as H265 "); + /* drop connection */ + conn_reset = true; + break; + } + } else if (codec != VIDEO_CODEC_H265) { + logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "invalid video codec change to H265: codec was set previously"); + /* drop connection */ + conn_reset = true; + break; + } unsigned char vps_start_code[] = { 0xa0, 0x00, 0x01, 0x00 }; unsigned char sps_start_code[] = { 0xa1, 0x00, 0x01, 0x00 }; unsigned char pps_start_code[] = { 0xa2, 0x00, 0x01, 0x00 }; @@ -692,9 +703,21 @@ raop_rtp_mirror_thread(void *arg) ptr += 4; memcpy(ptr, pps, pps_size); } else { - codec = VIDEO_CODEC_H264; - h265_video = false; - raop_rtp_mirror->callbacks.video_set_codec(raop_rtp_mirror->callbacks.cls, codec); + if (codec == VIDEO_CODEC_UNKNOWN) { + codec = VIDEO_CODEC_H264; + h265_video = false; + if (raop_rtp_mirror->callbacks.video_set_codec(raop_rtp_mirror->callbacks.cls, codec) < 0) { + logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "failed to set video codec as H264 "); + /* drop connection */ + conn_reset = true; + break; + } + } else if (codec != VIDEO_CODEC_H264) { + logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "invalid codec change to H264: codec was set previously"); + /* drop connection */ + conn_reset = true; + break; + } short sps_size = byteutils_get_short_be(payload,6); unsigned char *sequence_parameter_set = payload + 8; short pps_size = byteutils_get_short_be(payload, sps_size + 9); diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c index abad694..1d6e629 100644 --- a/renderers/video_renderer.c +++ b/renderers/video_renderer.c @@ -54,7 +54,7 @@ struct video_renderer_s { GstElement *appsrc, *pipeline; GstBus *bus; const char *codec; - bool autovideo, state_pending; + bool autovideo; int id; gboolean terminate; gint64 duration; @@ -158,7 +158,7 @@ GstElement *make_video_sink(const char *videosink, const char *videosink_options return NULL; } - /* process the video_sink_optons */ + /* process the video_sink_options */ size_t len = strlen(videosink_options); if (!len) { return video_sink; @@ -398,23 +398,23 @@ void video_renderer_resume() { } void video_renderer_start() { + GstState state; + const gchar *state_name; if (hls_video) { - GstState state; renderer->bus = gst_element_get_bus(renderer->pipeline); gst_element_set_state (renderer->pipeline, GST_STATE_PAUSED); gst_element_get_state(renderer->pipeline, &state, NULL, 1000 * GST_MSECOND); - const gchar *state_name; state_name= gst_element_state_get_name(state); logger_log(logger, LOGGER_DEBUG, "video renderer_start: state %s", state_name); return; } /* when not hls, start both h264 and h265 pipelines; will shut down the "wrong" one when we know the codec */ for (int i = 0; i < n_renderers; i++) { - gst_element_set_state (renderer_type[i]->pipeline, GST_STATE_PLAYING); - if (renderer_type[i]->appsrc) { - gst_video_pipeline_base_time = gst_element_get_base_time(renderer_type[i]->appsrc); - } renderer_type[i]->bus = gst_element_get_bus(renderer_type[i]->pipeline); + gst_element_set_state (renderer_type[i]->pipeline, GST_STATE_PAUSED); + gst_element_get_state(renderer_type[i]->pipeline, &state, NULL, 1000 * GST_MSECOND); + state_name= gst_element_state_get_name(state); + logger_log(logger, LOGGER_DEBUG, "video renderer_start: renderer %d state %s", i, state_name); } renderer = NULL; first_packet = true; @@ -505,15 +505,21 @@ void video_renderer_stop() { } } -static void video_renderer_destroy_h26x(video_renderer_t *renderer) { +static void video_renderer_destroy_instance(video_renderer_t *renderer) { if (renderer) { + logger_log(logger, LOGGER_DEBUG,"destroying renderer instance %p", renderer); GstState state; + GstStateChangeReturn ret; gst_element_get_state(renderer->pipeline, &state, NULL, 100 * GST_MSECOND); + logger_log(logger, LOGGER_DEBUG,"pipeline state is %s", gst_element_state_get_name(state)); if (state != GST_STATE_NULL) { if (!hls_video) { gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc)); } - gst_element_set_state (renderer->pipeline, GST_STATE_NULL); + ret = gst_element_set_state (renderer->pipeline, GST_STATE_NULL); + logger_log(logger, LOGGER_DEBUG,"pipeline state change to NULL: %s", + gst_element_state_change_return_get_name(ret)); + gst_element_get_state(renderer->pipeline, NULL, NULL, 1000 * GST_MSECOND); } gst_object_unref(renderer->bus); if (renderer->appsrc) { @@ -534,9 +540,9 @@ static void video_renderer_destroy_h26x(video_renderer_t *renderer) { void video_renderer_destroy() { for (int i = 0; i < n_renderers; i++) { if (renderer_type[i]) { - video_renderer_destroy_h26x(renderer_type[i]); + video_renderer_destroy_instance(renderer_type[i]); } - } + } } gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void *loop) { @@ -642,15 +648,6 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); } - if (renderer_type[type]->state_pending && strstr(GST_MESSAGE_SRC_NAME(message), "pipeline")) { - GstState state; - gst_element_get_state(renderer_type[type]->pipeline, &state, NULL, 100 * GST_MSECOND); - if (state == GST_STATE_NULL) { - gst_element_set_state(renderer_type[type]->pipeline, GST_STATE_PLAYING); - } else if (state == GST_STATE_PLAYING) { - renderer_type[type]->state_pending = false; - } - } if (renderer_type[type]->autovideo) { char *sink = strstr(GST_MESSAGE_SRC_NAME(message), "-actual-sink-"); if (sink) { @@ -711,27 +708,41 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void return TRUE; } -void video_renderer_choose_codec (bool video_is_h265) { +int video_renderer_choose_codec (bool video_is_h265) { + video_renderer_t *renderer_used = NULL; + video_renderer_t *renderer_unused = NULL; g_assert(!hls_video); - /* set renderer to h264 or h265, depending on pps/sps received by raop_rtp_mirror */ - video_renderer_t *renderer_new = video_is_h265 ? renderer_type[1] : renderer_type[0]; - if (renderer == renderer_new) { - return; + if (n_renderers == 1) { + if (video_is_h265) { + logger_log(logger, LOGGER_ERR, "video is h265 but the -h265 option was not used"); + return -1; + } + renderer_used = renderer_type[0]; + } else { + renderer_used = video_is_h265 ? renderer_type[1] : renderer_type[0]; + renderer_unused = video_is_h265 ? renderer_type[0] : renderer_type[1]; } - video_renderer_t *renderer_prev = renderer; - renderer = renderer_new; + if (renderer_used == NULL) { + return -1; + } else if (renderer_used == renderer) { + return 0; + } else if (renderer) { + return -1; + } + renderer = renderer_used; + gst_element_set_state (renderer->pipeline, GST_STATE_PLAYING); + GstState old_state, new_state; + if (gst_element_get_state(renderer->pipeline, &old_state, &new_state, 100 * GST_MSECOND) == GST_STATE_CHANGE_FAILURE) { + g_error("video pipeline failed to go into playing state"); + return -1; + } + logger_log(logger, LOGGER_DEBUG, "video_pipeline state change from %s to %s\n", + gst_element_state_get_name (old_state),gst_element_state_get_name (new_state)); gst_video_pipeline_base_time = gst_element_get_base_time(renderer->appsrc); - /* it seems unlikely that the codec will change between h264 and h265 during a connection, - * but in case it does, we set the previous renderer to GST_STATE_NULL, detect - * when this is finished by listening for the bus message, and then reset it to - * GST_STATE_READY, so it can be reused if the codec changes again. */ - if (renderer_prev) { - gst_app_src_end_of_stream (GST_APP_SRC(renderer_prev->appsrc)); - gst_bus_set_flushing(renderer_prev->bus, TRUE); - /* set state of previous renderer to GST_STATE_NULL to (hopefully?) close its video window */ - gst_element_set_state (renderer_prev->pipeline, GST_STATE_NULL); - renderer_prev->state_pending = true; // will set state to PLAYING once state is NULL + if (renderer_unused) { + gst_element_set_state (renderer_unused->pipeline, GST_STATE_NULL); } + return 0; } unsigned int video_reset_callback(void * loop) { diff --git a/renderers/video_renderer.h b/renderers/video_renderer.h index f1500fb..9e2e944 100644 --- a/renderers/video_renderer.h +++ b/renderers/video_renderer.h @@ -64,7 +64,7 @@ void video_renderer_destroy (); void video_renderer_size(float *width_source, float *height_source, float *width, float *height); bool waiting_for_x11_window(); bool video_get_playback_info(double *duration, double *position, float *rate); -void video_renderer_choose_codec(bool is_h265); +int video_renderer_choose_codec(bool is_h265); unsigned int video_renderer_listen(void *loop, int id); unsigned int video_reset_callback(void *loop); #ifdef __cplusplus diff --git a/uxplay.cpp b/uxplay.cpp index 8615c80..605e737 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -1579,11 +1579,9 @@ extern "C" void video_reset(void *cls) { reset_loop = true; } -extern "C" void video_set_codec(void *cls, video_codec_t codec) { - if (use_video) { - bool video_is_h265 = (codec == VIDEO_CODEC_H265); - video_renderer_choose_codec(video_is_h265); - } +extern "C" int video_set_codec(void *cls, video_codec_t codec) { + bool video_is_h265 = (codec == VIDEO_CODEC_H265); + return video_renderer_choose_codec(video_is_h265); } extern "C" void display_pin(void *cls, char *pin) {