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