Home | History | Annotate | Download | only in sink
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.accessorydisplay.sink;
     18 
     19 import com.android.accessorydisplay.common.Protocol;
     20 import com.android.accessorydisplay.common.Service;
     21 import com.android.accessorydisplay.common.Transport;
     22 
     23 import android.content.Context;
     24 import android.graphics.Rect;
     25 import android.media.MediaCodec;
     26 import android.media.MediaCodec.BufferInfo;
     27 import android.media.MediaFormat;
     28 import android.os.Handler;
     29 import android.view.Surface;
     30 import android.view.SurfaceHolder;
     31 import android.view.SurfaceView;
     32 
     33 import java.io.IOException;
     34 import java.nio.ByteBuffer;
     35 
     36 public class DisplaySinkService extends Service implements SurfaceHolder.Callback {
     37     private final ByteBuffer mBuffer = ByteBuffer.allocate(12);
     38     private final Handler mTransportHandler;
     39     private final int mDensityDpi;
     40 
     41     private SurfaceView mSurfaceView;
     42 
     43     // These fields are guarded by the following lock.
     44     // This is to ensure that the surface lifecycle is respected.  Although decoding
     45     // happens on the transport thread, we are not allowed to access the surface after
     46     // it is destroyed by the UI thread so we need to stop the codec immediately.
     47     private final Object mSurfaceAndCodecLock = new Object();
     48     private Surface mSurface;
     49     private int mSurfaceWidth;
     50     private int mSurfaceHeight;
     51     private MediaCodec mCodec;
     52     private ByteBuffer[] mCodecInputBuffers;
     53     private BufferInfo mCodecBufferInfo;
     54 
     55     public DisplaySinkService(Context context, Transport transport, int densityDpi) {
     56         super(context, transport, Protocol.DisplaySinkService.ID);
     57         mTransportHandler = transport.getHandler();
     58         mDensityDpi = densityDpi;
     59     }
     60 
     61     public void setSurfaceView(final SurfaceView surfaceView) {
     62         if (mSurfaceView != surfaceView) {
     63             final SurfaceView oldSurfaceView = mSurfaceView;
     64             mSurfaceView = surfaceView;
     65 
     66             if (oldSurfaceView != null) {
     67                 oldSurfaceView.post(new Runnable() {
     68                     @Override
     69                     public void run() {
     70                         final SurfaceHolder holder = oldSurfaceView.getHolder();
     71                         holder.removeCallback(DisplaySinkService.this);
     72                         updateSurfaceFromUi(null);
     73                     }
     74                 });
     75             }
     76 
     77             if (surfaceView != null) {
     78                 surfaceView.post(new Runnable() {
     79                     @Override
     80                     public void run() {
     81                         final SurfaceHolder holder = surfaceView.getHolder();
     82                         holder.addCallback(DisplaySinkService.this);
     83                         updateSurfaceFromUi(holder);
     84                     }
     85                 });
     86             }
     87         }
     88     }
     89 
     90     @Override
     91     public void onMessageReceived(int service, int what, ByteBuffer content) {
     92         switch (what) {
     93             case Protocol.DisplaySinkService.MSG_QUERY: {
     94                 getLogger().log("Received MSG_QUERY.");
     95                 sendSinkStatus();
     96                 break;
     97             }
     98 
     99             case Protocol.DisplaySinkService.MSG_CONTENT: {
    100                 decode(content);
    101                 break;
    102             }
    103         }
    104     }
    105 
    106     @Override
    107     public void surfaceCreated(SurfaceHolder holder) {
    108         // Ignore.  Wait for surface changed event that follows.
    109     }
    110 
    111     @Override
    112     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    113         updateSurfaceFromUi(holder);
    114     }
    115 
    116     @Override
    117     public void surfaceDestroyed(SurfaceHolder holder) {
    118         updateSurfaceFromUi(null);
    119     }
    120 
    121     private void updateSurfaceFromUi(SurfaceHolder holder) {
    122         Surface surface = null;
    123         int width = 0, height = 0;
    124         if (holder != null && !holder.isCreating()) {
    125             surface = holder.getSurface();
    126             if (surface.isValid()) {
    127                 final Rect frame = holder.getSurfaceFrame();
    128                 width = frame.width();
    129                 height = frame.height();
    130             } else {
    131                 surface = null;
    132             }
    133         }
    134 
    135         synchronized (mSurfaceAndCodecLock) {
    136             if (mSurface == surface &&  mSurfaceWidth == width && mSurfaceHeight == height) {
    137                 return;
    138             }
    139 
    140             mSurface = surface;
    141             mSurfaceWidth = width;
    142             mSurfaceHeight = height;
    143 
    144             if (mCodec != null) {
    145                 mCodec.stop();
    146                 mCodec = null;
    147                 mCodecInputBuffers = null;
    148                 mCodecBufferInfo = null;
    149             }
    150 
    151             if (mSurface != null) {
    152                 MediaFormat format = MediaFormat.createVideoFormat(
    153                         "video/avc", mSurfaceWidth, mSurfaceHeight);
    154                 try {
    155                     mCodec = MediaCodec.createDecoderByType("video/avc");
    156                 } catch (IOException e) {
    157                     throw new RuntimeException(
    158                             "failed to create video/avc decoder", e);
    159                 }
    160                 mCodec.configure(format, mSurface, null, 0);
    161                 mCodec.start();
    162                 mCodecBufferInfo = new BufferInfo();
    163             }
    164 
    165             mTransportHandler.post(new Runnable() {
    166                 @Override
    167                 public void run() {
    168                     sendSinkStatus();
    169                 }
    170             });
    171         }
    172     }
    173 
    174     private void decode(ByteBuffer content) {
    175         if (content == null) {
    176             return;
    177         }
    178         synchronized (mSurfaceAndCodecLock) {
    179             if (mCodec == null) {
    180                 return;
    181             }
    182 
    183             while (content.hasRemaining()) {
    184                 if (!provideCodecInputLocked(content)) {
    185                     getLogger().log("Dropping content because there are no available buffers.");
    186                     return;
    187                 }
    188 
    189                 consumeCodecOutputLocked();
    190             }
    191         }
    192     }
    193 
    194     private boolean provideCodecInputLocked(ByteBuffer content) {
    195         final int index = mCodec.dequeueInputBuffer(0);
    196         if (index < 0) {
    197             return false;
    198         }
    199         if (mCodecInputBuffers == null) {
    200             mCodecInputBuffers = mCodec.getInputBuffers();
    201         }
    202         final ByteBuffer buffer = mCodecInputBuffers[index];
    203         final int capacity = buffer.capacity();
    204         buffer.clear();
    205         if (content.remaining() <= capacity) {
    206             buffer.put(content);
    207         } else {
    208             final int limit = content.limit();
    209             content.limit(content.position() + capacity);
    210             buffer.put(content);
    211             content.limit(limit);
    212         }
    213         buffer.flip();
    214         mCodec.queueInputBuffer(index, 0, buffer.limit(), 0, 0);
    215         return true;
    216     }
    217 
    218     private void consumeCodecOutputLocked() {
    219         for (;;) {
    220             final int index = mCodec.dequeueOutputBuffer(mCodecBufferInfo, 0);
    221             if (index >= 0) {
    222                 mCodec.releaseOutputBuffer(index, true /*render*/);
    223             } else if (index != MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
    224                     && index != MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    225                 break;
    226             }
    227         }
    228     }
    229 
    230     private void sendSinkStatus() {
    231         synchronized (mSurfaceAndCodecLock) {
    232             if (mCodec != null) {
    233                 mBuffer.clear();
    234                 mBuffer.putInt(mSurfaceWidth);
    235                 mBuffer.putInt(mSurfaceHeight);
    236                 mBuffer.putInt(mDensityDpi);
    237                 mBuffer.flip();
    238                 getTransport().sendMessage(Protocol.DisplaySourceService.ID,
    239                         Protocol.DisplaySourceService.MSG_SINK_AVAILABLE, mBuffer);
    240             } else {
    241                 getTransport().sendMessage(Protocol.DisplaySourceService.ID,
    242                         Protocol.DisplaySourceService.MSG_SINK_NOT_AVAILABLE, null);
    243             }
    244         }
    245     }
    246 }
    247