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