Zoccadoum
Zoccadoum

Reputation: 872

How to efficiently pass streamed data from a service to an activity?

I'm developing a product that will interface with an external server application. I cannot change the protocol or format of the external server or the incoming data.

My application runs (mainly) as a service which receives packets over the network from the external service. Many of these packets are informational, however the external service can choose to send my application "video" data. This data is in a proprietary format and comes as separate encoded "frames" of data. My service decodes the proprietary format into bitmap data and sends it (as individual frames) to my "display video" activity.

Since these frames can come many times a second, I want the streaming to be as "jenk" free as possible. I'm looking for the most efficient way to pass each frame from my service to the display activity. I have come up with three methods but my question is which is the most efficient, (if any) and if you have any suggestions.

I'll explain each method, and show a bit of sample code for each. NOTE: the code I've included is simplified, but represents "existing functioning code" I've used each of these methods in various applications but would like some opinions as to the best suited for this particular application. I know opinions are like behinds. Everyone has one...

Method 1: "bound service message"

When the "display video" activity starts, it binds to the service and sets up a MessageHandler. The Service uses this hander to send each "frame" to the activity which in turn paints the frame to the surface view.

Code:

My activity binds to the service in onResume()

 void doBindService()
 {
     bindService(new Intent( this, com.myservice.class ), mConnection, Context.BIND_AUTO_CREATE );
 }

And in onServiceConnected() I send the service a reference to my Messenger.

public void onServiceConnected( ComponentName className, Ibinder service )
{
    m_service = new Messenger( service );
    Message msg = Message.obtain(null, MyService.MSG_REGISTER_CLIENT );
    msg.replyTo = mMessenger;
    msg.arg1 = getServiceClientType(); 
    m_service.send(msg);
}

When the service message handler receives the MSG_REGISTER_CLIENT message, it stores the "replyTo" of the connecting activity.

protected class IncomingHandler extends Handler 
{
    @Override
    public void handleMessage(Message msg)
    {
        MessageParcel p = null;
        Bundle b = msg.getData();
        switch ( msg.what )
        {
             case MSG_REGISTER_CLIENT:
             {
                 m_client_messenger = msg.replyTo;
                 break;
             }

             .....

        }
    }

}

When I receive a video frame from the server, I send the frame to the activity using the stored Messenger.

protected void sendClientFrame( byte[] frameData )
{
    Message message = Message.obtain();
    Bundle b = new Bundle();
    b.putByteArray( "frame_data", frameData );
    message.setData(b);
    message.what = MyActivity.MSG_FRAME_DATA;
    message.replyTo( mMessenger );
    if ( m_client_messenger != null )
        m_client_messenger.send(message);
}

When the activity receives this message, it paints:

class IncomingServiceMessageHandler extends Handler
{
    @Override
    public void handleMessage( Mesasge msg )
    {
        super.handleMessage( msg );
        switch ( msg.what )
        {
            case MyActivity.MSG_FRAME_DATA:
                Bundle b = msg.getData();
                b.setClassLoader( getClassLoader() );
                byte[] frame_data = b.getByteArray( "frame_data" );
                if ( frame_data != null )
                     decode_and_paint_frame_data( frame_data );
                break;

             case ....:
        }

    }

}

Method 2: "Intent Frame"

This method packs each incoming frame into an intent and send the intent to the view activity.

Code:

When the service receives a video frame, it creates an intent and packs the frame data into the intent and fires it off to the activity.

public void sendShowVideoActivityFrame( byte[] framedata )
{
    Intent frame_intent = new Intent( this, MyActivity.class );
    frame_intent.setFlags( Intent.FLAG_ACTIVITY_RENDER_TO_FRONT );
    frame_intent.setAction( MyActivity.ACTION_SEND_FRAME );
    frame_intent.startActivity( );
    frame_intent.putExtra( "frame_data", framedata );

    startActivity( frame_intent );

}

When the Activity receives the intent, it decodes and paints the frame data:

@Override
protected void onNewIntent( Intent intent )
{
    super.onNewIntent(intent);

    if ( intent.getAction().compareTo( MyActivity.ACTION_SEND_FRAME ) == 0 )
    {
        byte[] frame_data = intent.getByteArrayExtra( "frame_data" );
        if ( frame_data != null )
            decode_and_paint_frame_data( frame_data );

    }
}

Method 3: "Send through local socket"

When the Service receives a video frame it connects to the "display video" activity via a local socket. This socket is then used to "stream" each frame to the activity as it's received by the service.

Code:

The service creates a socket and connects to the activity:

protected Socket m_activity_socket = null;
protected InputStream m_nis = null;
protected OutputStrea m_nos = null;

protected void sendClientFrame( byte[] frameData )
{
    if ( m_activity_socket == null )
    {
         m_activity_socket = new Socket()
         SocketAddress sockaddr = new InetSocketAddress("127.0.0.1", 12345 );
         m_activity_socket.connect( sockaddr, 5000 );
                if (m_activity_socket.isConnected()) { 
                    m_nis = m_activity_socket.getInputStream();
                    m_nos = m_activity_socket.getOutputStream();
    }

    if ( m_nos != null )
    {
         // the header describes the frame data (type, width, height, length)
         // frame width and heigth have been previously decoded
         byte[] header = new byte[16];
         build_header( 1, width, height, frameData.length, header, 1 );
         m_nos.write( header );  // message header
         m_nos.write( frameData );  // message data
    }
}

When the client receives the message from the socket it decodes the frame data and paints it.

public class SocketServerListener implements Runnable
{
    private boolean _run = false;

    public void run()
    {
         Socket s = null;
         _run = true;
         serverSocket = new ServerSocket( 12345 );

         while( _run )
         {
             s = serverSocket.accept();

             handleSocket( s );
         }

         serverSocket.close();
    }

    public void handleSocket( Socket s )
    {
        InputStream in = s.getInputStream();
        OutputStream out = s.getOutputStream();
        int bytes_read = 0;

        byte[] header = new byte[16];
        bytes_read = in.read( buffer, 0, header.length );

        // the header contains the number of bytes in the frame data
        int data_len = header_get_frame_length( );

        byte[] frame_data = new byte[data_len];
        bytes_read = in.read( frame_data, 0, frame_data.length );
        if ( bytes_read == data_len ) // we got the full frame
             decode_and_paint_frame_data( frame_data );    
    }

}

Thanks in advance for your constructive comments.

Upvotes: 3

Views: 1871

Answers (2)

bukkojot
bukkojot

Reputation: 1530

Since Service and Activity lives in same process (if not specified other), you can just use static variables / singleton pattern. It haves zero overhead.

Upvotes: 0

Zoccadoum
Zoccadoum

Reputation: 872

After hours of research and days of experimentation, trial and error I decided on the last method described. My service sends an Intent to get my activity started and then waits for the activity to start a ServerSocket listening on a given local unprivileged port. Once the activity is ready, it starts a thread with a socket listening on the specified port. The service then connects to that port using a Socket and sends the frame data. In the sample code above, it would appear that I'm re-connecting for every frame. That is not the case in the real code. Once the service is connected to the socket, it simply keeps it open and sends each frame of data as it is received from the content server.

I chose this method because it seemed the most efficient and I'm quite familiar with socket programming. Sending the frames through service messages, or through intents seemed massively inefficient and introduced lag in places I had no control over.

Upvotes: 2

Related Questions