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