DisplaySinkService.java revision 8f3b1307678fcd1896c7fb8ba4cc20553dc032e8
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.nio.ByteBuffer; 34 35public class DisplaySinkService extends Service implements SurfaceHolder.Callback { 36 private final ByteBuffer mBuffer = ByteBuffer.allocate(12); 37 private final Handler mTransportHandler; 38 private final int mDensityDpi; 39 40 private SurfaceView mSurfaceView; 41 42 // These fields are guarded by the following lock. 43 // This is to ensure that the surface lifecycle is respected. Although decoding 44 // happens on the transport thread, we are not allowed to access the surface after 45 // it is destroyed by the UI thread so we need to stop the codec immediately. 46 private final Object mSurfaceAndCodecLock = new Object(); 47 private Surface mSurface; 48 private int mSurfaceWidth; 49 private int mSurfaceHeight; 50 private MediaCodec mCodec; 51 private ByteBuffer[] mCodecInputBuffers; 52 private BufferInfo mCodecBufferInfo; 53 54 public DisplaySinkService(Context context, Transport transport, int densityDpi) { 55 super(context, transport, Protocol.DisplaySinkService.ID); 56 mTransportHandler = transport.getHandler(); 57 mDensityDpi = densityDpi; 58 } 59 60 public void setSurfaceView(final SurfaceView surfaceView) { 61 if (mSurfaceView != surfaceView) { 62 final SurfaceView oldSurfaceView = mSurfaceView; 63 mSurfaceView = surfaceView; 64 65 if (oldSurfaceView != null) { 66 oldSurfaceView.post(new Runnable() { 67 @Override 68 public void run() { 69 final SurfaceHolder holder = oldSurfaceView.getHolder(); 70 holder.removeCallback(DisplaySinkService.this); 71 updateSurfaceFromUi(null); 72 } 73 }); 74 } 75 76 if (surfaceView != null) { 77 surfaceView.post(new Runnable() { 78 @Override 79 public void run() { 80 final SurfaceHolder holder = surfaceView.getHolder(); 81 holder.addCallback(DisplaySinkService.this); 82 updateSurfaceFromUi(holder); 83 } 84 }); 85 } 86 } 87 } 88 89 @Override 90 public void onMessageReceived(int service, int what, ByteBuffer content) { 91 switch (what) { 92 case Protocol.DisplaySinkService.MSG_QUERY: { 93 getLogger().log("Received MSG_QUERY."); 94 sendSinkStatus(); 95 break; 96 } 97 98 case Protocol.DisplaySinkService.MSG_CONTENT: { 99 decode(content); 100 break; 101 } 102 } 103 } 104 105 @Override 106 public void surfaceCreated(SurfaceHolder holder) { 107 // Ignore. Wait for surface changed event that follows. 108 } 109 110 @Override 111 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 112 updateSurfaceFromUi(holder); 113 } 114 115 @Override 116 public void surfaceDestroyed(SurfaceHolder holder) { 117 updateSurfaceFromUi(null); 118 } 119 120 private void updateSurfaceFromUi(SurfaceHolder holder) { 121 Surface surface = null; 122 int width = 0, height = 0; 123 if (holder != null && !holder.isCreating()) { 124 surface = holder.getSurface(); 125 if (surface.isValid()) { 126 final Rect frame = holder.getSurfaceFrame(); 127 width = frame.width(); 128 height = frame.height(); 129 } else { 130 surface = null; 131 } 132 } 133 134 synchronized (mSurfaceAndCodecLock) { 135 if (mSurface == surface && mSurfaceWidth == width && mSurfaceHeight == height) { 136 return; 137 } 138 139 mSurface = surface; 140 mSurfaceWidth = width; 141 mSurfaceHeight = height; 142 143 if (mCodec != null) { 144 mCodec.stop(); 145 mCodec = null; 146 mCodecInputBuffers = null; 147 mCodecBufferInfo = null; 148 } 149 150 if (mSurface != null) { 151 MediaFormat format = MediaFormat.createVideoFormat( 152 "video/avc", mSurfaceWidth, mSurfaceHeight); 153 mCodec = MediaCodec.createDecoderByType("video/avc"); 154 mCodec.configure(format, mSurface, null, 0); 155 mCodec.start(); 156 mCodecBufferInfo = new BufferInfo(); 157 } 158 159 mTransportHandler.post(new Runnable() { 160 @Override 161 public void run() { 162 sendSinkStatus(); 163 } 164 }); 165 } 166 } 167 168 private void decode(ByteBuffer content) { 169 if (content == null) { 170 return; 171 } 172 synchronized (mSurfaceAndCodecLock) { 173 if (mCodec == null) { 174 return; 175 } 176 177 while (content.hasRemaining()) { 178 if (!provideCodecInputLocked(content)) { 179 getLogger().log("Dropping content because there are no available buffers."); 180 return; 181 } 182 183 consumeCodecOutputLocked(); 184 } 185 } 186 } 187 188 private boolean provideCodecInputLocked(ByteBuffer content) { 189 final int index = mCodec.dequeueInputBuffer(0); 190 if (index < 0) { 191 return false; 192 } 193 if (mCodecInputBuffers == null) { 194 mCodecInputBuffers = mCodec.getInputBuffers(); 195 } 196 final ByteBuffer buffer = mCodecInputBuffers[index]; 197 final int capacity = buffer.capacity(); 198 buffer.clear(); 199 if (content.remaining() <= capacity) { 200 buffer.put(content); 201 } else { 202 final int limit = content.limit(); 203 content.limit(content.position() + capacity); 204 buffer.put(content); 205 content.limit(limit); 206 } 207 buffer.flip(); 208 mCodec.queueInputBuffer(index, 0, buffer.limit(), 0, 0); 209 return true; 210 } 211 212 private void consumeCodecOutputLocked() { 213 for (;;) { 214 final int index = mCodec.dequeueOutputBuffer(mCodecBufferInfo, 0); 215 if (index >= 0) { 216 mCodec.releaseOutputBuffer(index, true /*render*/); 217 } else if (index != MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED 218 && index != MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 219 break; 220 } 221 } 222 } 223 224 private void sendSinkStatus() { 225 synchronized (mSurfaceAndCodecLock) { 226 if (mCodec != null) { 227 mBuffer.clear(); 228 mBuffer.putInt(mSurfaceWidth); 229 mBuffer.putInt(mSurfaceHeight); 230 mBuffer.putInt(mDensityDpi); 231 mBuffer.flip(); 232 getTransport().sendMessage(Protocol.DisplaySourceService.ID, 233 Protocol.DisplaySourceService.MSG_SINK_AVAILABLE, mBuffer); 234 } else { 235 getTransport().sendMessage(Protocol.DisplaySourceService.ID, 236 Protocol.DisplaySourceService.MSG_SINK_NOT_AVAILABLE, null); 237 } 238 } 239 } 240} 241