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