hzeller / gmrender-resurrect

Resource efficient UPnP/DLNA renderer, optimal for Raspberry Pi, CuBox or a general MediaServer. Fork of GMediaRenderer to add some features to make it usable.
GNU General Public License v2.0
851 stars 206 forks source link

gmediarenderer hangs up with Bluetooth speaker #198

Open ASimb opened 4 years ago

ASimb commented 4 years ago

Hi, I have two Raspberry PI running with gmediarenderer (PI4B and PI3A). The smaller one is connected to the HiFi via plug => AUX and everything runs perfectly. The other one is connected via bluealsa to BT speakers. I found out, that every time a song runs out OR I try to start "PLAY" if no URI is present, the gmediarenderer hangs up completely. I had to disconnect and reconnect the speakers to get it work again. I found out, that two modifications in the output_gstreamer.c solved the problem (for me):

static int output_gstreamer_play(output_transition_cb_t callback) {

        //ASimb: the following lines ensure, that no empty URI  
        //       is transmitted to the GStreamer
        if (gsuri_==NULL) {
            Log_info("gstreamer", "setting play state failed (No URI to play)");
                return -1;
        }

    play_trans_callback_ = callback;
    if (get_current_player_state() != GST_STATE_PAUSED) {
        if (gst_element_set_state(player_, GST_STATE_READY) ==
            GST_STATE_CHANGE_FAILURE) {
            Log_error("gstreamer", "setting play state failed (1)");
            // Error, but continue; can't get worse :)
        }
        g_object_set(G_OBJECT(player_), "uri", gsuri_, NULL);
    }
    if (gst_element_set_state(player_, GST_STATE_PLAYING) ==
        GST_STATE_CHANGE_FAILURE) {
        Log_error("gstreamer", "setting play state failed (2)");
        return -1;
    }
    return 0;
}

and

static gboolean my_bus_callback(GstBus * bus, GstMessage * msg,
                gpointer data)
{
    (void)bus;
    (void)data;

    GstMessageType msgType;
    const GstObject *msgSrc;
    const gchar *msgSrcName;

    msgType = GST_MESSAGE_TYPE(msg);
    msgSrc = GST_MESSAGE_SRC(msg);
    msgSrcName = GST_OBJECT_NAME(msgSrc);

    switch (msgType) {
    case GST_MESSAGE_EOS:
        Log_info("gstreamer", "%s: End-of-stream", msgSrcName);
        if (gs_next_uri_ != NULL) {
            // If playbin does not support gapless (old
            // versions didn't), this will trigger.
            free(gsuri_);
            gsuri_ = gs_next_uri_;
            gs_next_uri_ = NULL;
            gst_element_set_state(player_, GST_STATE_READY);
            g_object_set(G_OBJECT(player_), "uri", gsuri_, NULL);
            gst_element_set_state(player_, GST_STATE_PLAYING);
            if (play_trans_callback_) {
                play_trans_callback_(PLAY_STARTED_NEXT_STREAM);
            }
        } else {
                        //ASimb: the second line is needed to ensure, 
                        //       that the GStreamer really stops
                Log_info("gstreamer", "%s: No further stream available", msgSrcName);
                        gst_element_set_state(player_, GST_STATE_READY);

                        if (play_trans_callback_) {
                    play_trans_callback_(PLAY_STOPPED);
                        }
        }
        break;

    case GST_MESSAGE_ERROR: {
        gchar *debug;
        GError *err;

        gst_message_parse_error(msg, &err, &debug);

        Log_error("gstreamer", "%s: Error: %s (Debug: %s)",
              msgSrcName, err->message, debug);
        g_error_free(err);
        g_free(debug);

        break;
    }
    case GST_MESSAGE_STATE_CHANGED: {
        GstState oldstate, newstate, pending;
        gst_message_parse_state_changed(msg, &oldstate, &newstate,
                        &pending);
        /*
        g_print("GStreamer: %s: State change: '%s' -> '%s', "
            "PENDING: '%s'\n", msgSrcName,
            gststate_get_name(oldstate),
            gststate_get_name(newstate),
            gststate_get_name(pending));
        */
        break;
    }

    case GST_MESSAGE_TAG: {
        GstTagList *tags = NULL;

        if (meta_update_callback_ != NULL) {
            gst_message_parse_tag(msg, &tags);
            /*g_print("GStreamer: Got tags from element %s\n",
                GST_OBJECT_NAME (msg->src));
            */
            struct MetaModify modify;
            modify.meta = &song_meta_;
            modify.any_change = 0;
            gst_tag_list_foreach(tags, &MetaModify_add_tag, &modify);
            gst_tag_list_free(tags);
            if (modify.any_change) {
                meta_update_callback_(&song_meta_);
            }
        }
        break;
    }

    case GST_MESSAGE_BUFFERING:
        {
                if (buffer_duration <= 0.0) break;  /* nothing to buffer */

                gint percent = 0;
                gst_message_parse_buffering (msg, &percent);

                /* Pause playback until buffering is complete. */
                if (percent < 100)
                        gst_element_set_state(player_, GST_STATE_PAUSED);
                else
                        gst_element_set_state(player_, GST_STATE_PLAYING);
        break;
        }
    default:
        /*
        g_print("GStreamer: %s: unhandled message type %d (%s)\n",
                msgSrcName, msgType, gst_message_type_get_name(msgType));
        */
        break;
    }

    return TRUE;
}
hzeller commented 4 years ago

thanks! Can you provide this as a diff to easier see the changes ? Just do a git diff on the modified file in your checkout.

ASimb commented 4 years ago

Sorry, I'm a newbe here, so I don't know how to do a git diff :-) I try a more classic way: in the output_gstreamer_play() I added the following lines at the begining:

//ASimb: the following lines ensure, that no empty URI  
//       is transmitted to the GStreamer
if (gsuri_==NULL) {
        Log_info("gstreamer", "setting play state failed (No URI to play)");
        return -1;
        }

and in my_bus_callback() I modified the lines at the GST_MESSAGE_EOS case after the "else":

...
} else {
        //ASimb: the second line is needed to ensure, 
        //       that the GStreamer really stops
        Log_info("gstreamer", "%s: No further stream available", msgSrcName);
        gst_element_set_state(player_, GST_STATE_READY);

        if (play_trans_callback_) {
              play_trans_callback_(PLAY_STOPPED);
              }
         }
}

For details please see my post at the begining

regards

Andreas

mill1000 commented 4 years ago

@ASimb What is your setup for gmrender-resurrect & bluealsa?

ASimb commented 4 years ago

@ASimb What is your setup for gmrender-resurrect & bluealsa?

asound.conf

#defaults.pcm.!card "btspeaker"
#defaults.ctl.!card "btspeaker"

pcm.btspeaker {
   type plug
   slave.pcm {
      type bluealsa
      service org.bluealsa
      device "XX:XX:XX:XX:XX:XX" 
      profile "a2dp"
      }
   hint {
      show on
      description "BT speaker (Get Together Mini)"
      }
   }

ctl.btspeaker {
   type bluealsa
   }

pcm.!default {
   type plug
   fallback "sysdefault"
   slave.pcm "btspeaker"
   }

ctl.!default {
   type plug
   fallback "sysdefault"
   slave.ctl "btspeaker"
   }

#ctl.!default {
#   type hw
#   card 0
#   }

gmrenderer - start script:

#!/bin/sh

### BEGIN INIT INFO
# Provides: gmediarender
# Required-Start: $remote_fs $syslog $all 
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start GMediaRender at boot time
# Description: Start GMediaRender at boot time.
### END INIT INFO

# User and group the daemon will be running as. On the Raspberry Pi, let's use
# the default user.
DAEMON_USER="pi:audio"

# Device name as it will be advertised to and shown in the UPnP controller UI.
# Some string that helps you recognize the player, e.g. "Livingroom Player"
UPNP_DEVICE_NAME="Mediarender Keller"

# Initial volume in decibel. 0.0 is 'full volume', -10 correspondents to '75' on
# the exported volume scale (Note, this does not change the ALSA volume, only
# internal to gmrender. So make sure to leave the ALSA volume always to 100%).
INITIAL_VOLUME_DB=-10

# If you explicitly choose a specific ALSA device here (find them with 'aplay -L'), then
# gmediarenderer will use that ALSA device to play audio.
# Otherwise, whatever default is configured for gstreamer for the '$DAEMON_USER' is
# used.
ALSA_DEVICE="btspeaker"
#ALSA_DEVICE="sysdefault"
#ALSA_DEVICE="iec958"

#Wenn aktiviert wird ein logfile erstellt
#LOG_FILE_PARAM="--logfile /var/tmp/gmrenderer.log"

# Path to the gmediarender binary.
BINARY_PATH=/usr/local/bin/gmediarender

if [ -n "$ALSA_DEVICE" ] ; then
    GS_SINK_PARAM="--gstout-audiosink=alsasink"
    GS_DEVICE_PARAM="--gstout-audiodevice=$ALSA_DEVICE"
fi

# A simple stable UUID, based on this systems' first ethernet devices MAC address,
# only using tools readily available to generate.
UPNP_UUID=`ip link show | awk '/ether/ {print "salt:)-" $2}' | head -1 | md5sum | awk '{print $1}'`

USER=root
HOME=/root
export USER HOME
case "$1" in
    start)
        echo "Starting GMediaRender"
        start-stop-daemon -x $BINARY_PATH -c "$DAEMON_USER" -S -- -f "$UPNP_DEVICE_NAME" -d -u "$UPNP_UUID" $GS_SINK_PARAM $GS_DEVICE_PARAM --gstout-initial-volume-db=$INITIAL_VOLUME_DB $LOG_FILE_PARAM
        ;;

    stop)
        echo "Stopping GMediaRender"
        start-stop-daemon -x $BINARY_PATH -K
        ;;

    *)
        echo "Usage: /etc/init.d/gmediarender {start|stop}"
        exit 1
        ;;
esac

exit 0