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 17package com.android.accessorydisplay.sink; 18 19import com.android.accessorydisplay.common.Protocol; 20import com.android.accessorydisplay.common.Service; 21import com.android.accessorydisplay.common.Transport; 22 23import android.content.Context; 24import android.graphics.Rect; 25import android.media.MediaCodec; 26import android.media.MediaCodec.BufferInfo; 27import android.media.MediaFormat; 28import android.os.Handler; 29import android.view.Surface; 30import android.view.SurfaceHolder; 31import android.view.SurfaceView; 32 33import java.io.IOException; 34import java.nio.ByteBuffer; 35 36public 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