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.source; 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.hardware.display.DisplayManager; 25import android.hardware.display.VirtualDisplay; 26import android.media.MediaCodec; 27import android.media.MediaCodec.BufferInfo; 28import android.media.MediaCodecInfo; 29import android.media.MediaFormat; 30import android.os.Handler; 31import android.os.Message; 32import android.view.Display; 33import android.view.Surface; 34 35import java.io.IOException; 36import java.nio.ByteBuffer; 37 38public class DisplaySourceService extends Service { 39 private static final int MSG_DISPATCH_DISPLAY_ADDED = 1; 40 private static final int MSG_DISPATCH_DISPLAY_REMOVED = 2; 41 42 private static final String DISPLAY_NAME = "Accessory Display"; 43 private static final int BIT_RATE = 6000000; 44 private static final int FRAME_RATE = 30; 45 private static final int I_FRAME_INTERVAL = 10; 46 47 private final Callbacks mCallbacks; 48 private final ServiceHandler mHandler; 49 private final DisplayManager mDisplayManager; 50 51 private boolean mSinkAvailable; 52 private int mSinkWidth; 53 private int mSinkHeight; 54 private int mSinkDensityDpi; 55 56 private VirtualDisplayThread mVirtualDisplayThread; 57 58 public DisplaySourceService(Context context, Transport transport, Callbacks callbacks) { 59 super(context, transport, Protocol.DisplaySourceService.ID); 60 mCallbacks = callbacks; 61 mHandler = new ServiceHandler(); 62 mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); 63 } 64 65 @Override 66 public void start() { 67 super.start(); 68 69 getLogger().log("Sending MSG_QUERY."); 70 getTransport().sendMessage(Protocol.DisplaySinkService.ID, 71 Protocol.DisplaySinkService.MSG_QUERY, null); 72 } 73 74 @Override 75 public void stop() { 76 super.stop(); 77 78 handleSinkNotAvailable(); 79 } 80 81 @Override 82 public void onMessageReceived(int service, int what, ByteBuffer content) { 83 switch (what) { 84 case Protocol.DisplaySourceService.MSG_SINK_AVAILABLE: { 85 getLogger().log("Received MSG_SINK_AVAILABLE"); 86 if (content.remaining() >= 12) { 87 final int width = content.getInt(); 88 final int height = content.getInt(); 89 final int densityDpi = content.getInt(); 90 if (width >= 0 && width <= 4096 91 && height >= 0 && height <= 4096 92 && densityDpi >= 60 && densityDpi <= 640) { 93 handleSinkAvailable(width, height, densityDpi); 94 return; 95 } 96 } 97 getLogger().log("Receive invalid MSG_SINK_AVAILABLE message."); 98 break; 99 } 100 101 case Protocol.DisplaySourceService.MSG_SINK_NOT_AVAILABLE: { 102 getLogger().log("Received MSG_SINK_NOT_AVAILABLE"); 103 handleSinkNotAvailable(); 104 break; 105 } 106 } 107 } 108 109 private void handleSinkAvailable(int width, int height, int densityDpi) { 110 if (mSinkAvailable && mSinkWidth == width && mSinkHeight == height 111 && mSinkDensityDpi == densityDpi) { 112 return; 113 } 114 115 getLogger().log("Accessory display sink available: " 116 + "width=" + width + ", height=" + height 117 + ", densityDpi=" + densityDpi); 118 mSinkAvailable = true; 119 mSinkWidth = width; 120 mSinkHeight = height; 121 mSinkDensityDpi = densityDpi; 122 createVirtualDisplay(); 123 } 124 125 private void handleSinkNotAvailable() { 126 getLogger().log("Accessory display sink not available."); 127 128 mSinkAvailable = false; 129 mSinkWidth = 0; 130 mSinkHeight = 0; 131 mSinkDensityDpi = 0; 132 releaseVirtualDisplay(); 133 } 134 135 private void createVirtualDisplay() { 136 releaseVirtualDisplay(); 137 138 mVirtualDisplayThread = new VirtualDisplayThread( 139 mSinkWidth, mSinkHeight, mSinkDensityDpi); 140 mVirtualDisplayThread.start(); 141 } 142 143 private void releaseVirtualDisplay() { 144 if (mVirtualDisplayThread != null) { 145 mVirtualDisplayThread.quit(); 146 mVirtualDisplayThread = null; 147 } 148 } 149 150 public interface Callbacks { 151 public void onDisplayAdded(Display display); 152 public void onDisplayRemoved(Display display); 153 } 154 155 private final class ServiceHandler extends Handler { 156 @Override 157 public void handleMessage(Message msg) { 158 switch (msg.what) { 159 case MSG_DISPATCH_DISPLAY_ADDED: { 160 mCallbacks.onDisplayAdded((Display)msg.obj); 161 break; 162 } 163 164 case MSG_DISPATCH_DISPLAY_REMOVED: { 165 mCallbacks.onDisplayRemoved((Display)msg.obj); 166 break; 167 } 168 } 169 } 170 } 171 172 private final class VirtualDisplayThread extends Thread { 173 private static final int TIMEOUT_USEC = 1000000; 174 175 private final int mWidth; 176 private final int mHeight; 177 private final int mDensityDpi; 178 179 private volatile boolean mQuitting; 180 181 public VirtualDisplayThread(int width, int height, int densityDpi) { 182 mWidth = width; 183 mHeight = height; 184 mDensityDpi = densityDpi; 185 } 186 187 @Override 188 public void run() { 189 MediaFormat format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight); 190 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 191 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 192 format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); 193 format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 194 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL); 195 MediaCodec codec; 196 try { 197 codec = MediaCodec.createEncoderByType("video/avc"); 198 } catch (IOException e) { 199 throw new RuntimeException( 200 "failed to create video/avc encoder", e); 201 } 202 codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 203 Surface surface = codec.createInputSurface(); 204 codec.start(); 205 206 VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay( 207 DISPLAY_NAME, mWidth, mHeight, mDensityDpi, surface, 0); 208 if (virtualDisplay != null) { 209 mHandler.obtainMessage(MSG_DISPATCH_DISPLAY_ADDED, 210 virtualDisplay.getDisplay()).sendToTarget(); 211 212 stream(codec); 213 214 mHandler.obtainMessage(MSG_DISPATCH_DISPLAY_REMOVED, 215 virtualDisplay.getDisplay()).sendToTarget(); 216 virtualDisplay.release(); 217 } 218 219 codec.signalEndOfInputStream(); 220 codec.stop(); 221 } 222 223 public void quit() { 224 mQuitting = true; 225 } 226 227 private void stream(MediaCodec codec) { 228 BufferInfo info = new BufferInfo(); 229 ByteBuffer[] buffers = null; 230 while (!mQuitting) { 231 int index = codec.dequeueOutputBuffer(info, TIMEOUT_USEC); 232 if (index >= 0) { 233 if (buffers == null) { 234 buffers = codec.getOutputBuffers(); 235 } 236 237 ByteBuffer buffer = buffers[index]; 238 buffer.limit(info.offset + info.size); 239 buffer.position(info.offset); 240 241 getTransport().sendMessage(Protocol.DisplaySinkService.ID, 242 Protocol.DisplaySinkService.MSG_CONTENT, buffer); 243 codec.releaseOutputBuffer(index, false); 244 } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 245 buffers = null; 246 } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) { 247 getLogger().log("Codec dequeue buffer timed out."); 248 } 249 } 250 } 251 } 252} 253