OSDN Git Service

Bugfixes in MediaSource SurfaceTexture management.
[android-x86/system-media.git] / mca / filterpacks / videosrc / java / MediaSource.java
1 /*
2  * Copyright (C) 2011 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
17
18 package android.filterpacks.videosrc;
19
20 import android.content.Context;
21 import android.content.res.AssetFileDescriptor;
22 import android.filterfw.core.Filter;
23 import android.filterfw.core.FilterContext;
24 import android.filterfw.core.Frame;
25 import android.filterfw.core.FrameFormat;
26 import android.filterfw.core.FrameManager;
27 import android.filterfw.core.GenerateFieldPort;
28 import android.filterfw.core.GenerateFinalPort;
29 import android.filterfw.core.GLFrame;
30 import android.filterfw.core.KeyValueMap;
31 import android.filterfw.core.MutableFrameFormat;
32 import android.filterfw.core.NativeFrame;
33 import android.filterfw.core.Program;
34 import android.filterfw.core.ShaderProgram;
35 import android.filterfw.format.ImageFormat;
36 import android.graphics.SurfaceTexture;
37 import android.media.MediaPlayer;
38 import android.os.ConditionVariable;
39 import android.view.Surface;
40
41 import java.io.IOException;
42 import java.io.FileDescriptor;
43 import java.lang.IllegalArgumentException;
44 import java.util.List;
45 import java.util.Set;
46
47 import android.util.Log;
48
49 /**
50  * @hide
51  */
52 public class MediaSource extends Filter {
53
54     /** User-visible parameters */
55
56     /** The source URL for the media source. Can be an http: link to a remote
57      * resource, or a file: link to a local media file */
58     @GenerateFieldPort(name = "sourceUrl", hasDefault = true)
59     private String mSourceUrl = "";
60
61     /** An open asset file descriptor to a local media source. Default is null */
62     @GenerateFieldPort(name = "sourceAsset", hasDefault = true)
63     private AssetFileDescriptor mSourceAsset = null;
64
65     /** Whether the media source is a URL or an asset file descriptor. Defaults to false. */
66     @GenerateFieldPort(name = "sourceIsUrl", hasDefault = true)
67     private boolean mSelectedIsUrl = false;
68
69     /** Whether the filter will always wait for a new video frame, or whether it
70      * will output an old frame again if a new frame isn't available. Defaults to true.
71      */
72     @GenerateFinalPort(name = "waitForNewFrame", hasDefault = true)
73     private boolean mWaitForNewFrame = true;
74
75     /** Whether the media source should loop automatically or not. Defaults to true. */
76     @GenerateFieldPort(name = "loop", hasDefault = true)
77     private boolean mLooping = true;
78
79     /** Volume control. Currently sound is piped directly to the speakers, so this defaults to mute. */
80     @GenerateFieldPort(name = "volume", hasDefault = true)
81     private float mVolume = 0.f;
82
83     private MediaPlayer mMediaPlayer;
84     private GLFrame mMediaFrame;
85     private SurfaceTexture mSurfaceTexture;
86     private ShaderProgram mFrameExtractor;
87     private MutableFrameFormat mOutputFormat;
88     private float[] mFrameTransform;
89
90     // Total timeouts will be PREP_TIMEOUT*PREP_TIMEOUT_REPEAT
91     private static final int PREP_TIMEOUT = 100; // ms
92     private static final int PREP_TIMEOUT_REPEAT = 100;
93     private static final int NEWFRAME_TIMEOUT = 100; //ms
94     private static final int NEWFRAME_TIMEOUT_REPEAT = 10;
95
96     private final String mFrameShader =
97             "#extension GL_OES_EGL_image_external : require\n" +
98             "precision mediump float;\n" +
99             "uniform mat4 frame_transform;\n" +
100             "uniform samplerExternalOES tex_sampler_0;\n" +
101             "varying vec2 v_texcoord;\n" +
102             "void main() {\n" +
103             "  vec2 transformed_texcoord = (frame_transform * vec4(v_texcoord, 0., 1.) ).xy;" +
104             "  gl_FragColor = texture2D(tex_sampler_0, transformed_texcoord);\n" +
105             "}\n";
106
107     private boolean mGotSize;
108     private boolean mPrepared;
109     private boolean mPlaying;
110     private boolean mNewFrameAvailable;
111     private boolean mPaused;
112     private boolean mCompleted;
113
114     private final boolean mLogVerbose;
115     private static final String TAG = "MediaSource";
116
117     public MediaSource(String name) {
118         super(name);
119         mNewFrameAvailable = false;
120         mFrameTransform = new float[16];
121
122         mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
123     }
124
125     @Override
126     public void setupPorts() {
127         // Add input port
128         addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
129                                                   FrameFormat.TARGET_GPU));
130     }
131
132     private void createFormats() {
133         mOutputFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
134                                            FrameFormat.TARGET_GPU);
135     }
136
137     @Override
138     protected void prepare(FilterContext context) {
139         if (mLogVerbose) Log.v(TAG, "Preparing MediaSource");
140
141         mFrameExtractor = new ShaderProgram(context, mFrameShader);
142         // SurfaceTexture defines (0,0) to be bottom-left. The filter framework
143         // defines (0,0) as top-left, so do the flip here.
144         mFrameExtractor.setSourceRect(0, 1, 1, -1);
145
146         createFormats();
147     }
148
149     @Override
150     public void open(FilterContext context) {
151         if (mLogVerbose) {
152             Log.v(TAG, "Opening MediaSource");
153             if (mSelectedIsUrl) {
154                 Log.v(TAG, "Current URL is " + mSourceUrl);
155             } else {
156                 Log.v(TAG, "Current source is Asset!");
157             }
158         }
159
160         mMediaFrame = (GLFrame)context.getFrameManager().newBoundFrame(
161                 mOutputFormat,
162                 GLFrame.EXTERNAL_TEXTURE,
163                 0);
164
165         mSurfaceTexture = new SurfaceTexture(mMediaFrame.getTextureId());
166
167         if (!setupMediaPlayer(mSelectedIsUrl)) {
168           throw new RuntimeException("Error setting up MediaPlayer!");
169         }
170     }
171
172     @Override
173     public void process(FilterContext context) {
174         // Note: process is synchronized by its caller in the Filter base class
175         if (mLogVerbose) Log.v(TAG, "Processing new frame");
176
177         if (mMediaPlayer == null) {
178             // Something went wrong in initialization or parameter updates
179             throw new NullPointerException("Unexpected null media player!");
180         }
181
182         if (mCompleted) {
183             // Video playback is done, so close us down
184             closeOutputPort("video");
185             return;
186         }
187
188         if (!mPlaying) {
189             int waitCount = 0;
190             if (mLogVerbose) Log.v(TAG, "Waiting for preparation to complete");
191             while (!mGotSize || !mPrepared) {
192                 try {
193                     this.wait(PREP_TIMEOUT);
194                 } catch (InterruptedException e) {
195                     // ignoring
196                 }
197                 if (mCompleted) {
198                     // Video playback is done, so close us down
199                     closeOutputPort("video");
200                     return;
201                 }
202                 waitCount++;
203                 if (waitCount == PREP_TIMEOUT_REPEAT) {
204                     mMediaPlayer.release();
205                     throw new RuntimeException("MediaPlayer timed out while preparing!");
206                 }
207             }
208             if (mLogVerbose) Log.v(TAG, "Starting playback");
209             mMediaPlayer.start();
210         }
211
212         // Use last frame if paused, unless just starting playback, in which case
213         // we want at least one valid frame before pausing
214         if (!mPaused || !mPlaying) {
215             if (mWaitForNewFrame) {
216                 if (mLogVerbose) Log.v(TAG, "Waiting for new frame");
217
218                 int waitCount = 0;
219                 while (!mNewFrameAvailable) {
220                     if (waitCount == NEWFRAME_TIMEOUT_REPEAT) {
221                         if (mCompleted) {
222                             // Video playback is done, so close us down
223                             closeOutputPort("video");
224                             return;
225                         } else {
226                             throw new RuntimeException("Timeout waiting for new frame!");
227                         }
228                     }
229                     try {
230                         this.wait(NEWFRAME_TIMEOUT);
231                     } catch (InterruptedException e) {
232                         if (mLogVerbose) Log.v(TAG, "interrupted");
233                         // ignoring
234                     }
235                     waitCount++;
236                 }
237                 mNewFrameAvailable = false;
238                 if (mLogVerbose) Log.v(TAG, "Got new frame");
239             }
240
241             mSurfaceTexture.updateTexImage();
242
243             mSurfaceTexture.getTransformMatrix(mFrameTransform);
244             mFrameExtractor.setHostValue("frame_transform", mFrameTransform);
245         }
246
247         Frame output = context.getFrameManager().newFrame(mOutputFormat);
248         mFrameExtractor.process(mMediaFrame, output);
249
250         long timestamp = mSurfaceTexture.getTimestamp();
251         if (mLogVerbose) Log.v(TAG, "Timestamp: " + (timestamp / 1000000000.0) + " s");
252         output.setTimestamp(timestamp);
253
254         pushOutput("video", output);
255         output.release();
256
257         mPlaying = true;
258     }
259
260     @Override
261     public void close(FilterContext context) {
262         if (mMediaPlayer.isPlaying()) {
263             mMediaPlayer.stop();
264         }
265         mMediaPlayer.release();
266         mMediaPlayer = null;
267         mSurfaceTexture.release();
268         mSurfaceTexture = null;
269         if (mLogVerbose) Log.v(TAG, "MediaSource closed");
270     }
271
272     @Override
273     public void tearDown(FilterContext context) {
274         if (mMediaFrame != null) {
275             mMediaFrame.release();
276         }
277     }
278
279     // When updating the port values of the filter, users can update sourceIsUrl to switch
280     //   between using URL objects or Assets.
281     // If updating only sourceUrl/sourceAsset, MediaPlayer gets reset if the current player
282     //   uses Url objects/Asset.
283     // Otherwise the new sourceUrl/sourceAsset is stored and will be used when users switch
284     //   sourceIsUrl next time.
285     @Override
286     public void fieldPortValueUpdated(String name, FilterContext context) {
287         if (mLogVerbose) Log.v(TAG, "Parameter update");
288         if (name.equals("sourceUrl")) {
289            if (isOpen()) {
290                 if (mLogVerbose) Log.v(TAG, "Opening new source URL");
291                 if (mSelectedIsUrl) {
292                     setupMediaPlayer(mSelectedIsUrl);
293                 }
294             }
295         } else if (name.equals("sourceAsset") ) {
296             if (isOpen()) {
297                 if (mLogVerbose) Log.v(TAG, "Opening new source FD");
298                 if (!mSelectedIsUrl) {
299                     setupMediaPlayer(mSelectedIsUrl);
300                 }
301             }
302         } else if (name.equals("loop")) {
303             if (isOpen()) {
304                 mMediaPlayer.setLooping(mLooping);
305             }
306         } else if (name.equals("sourceIsUrl")) {
307             if (isOpen()){
308                 if (mSelectedIsUrl){
309                     if (mLogVerbose) Log.v(TAG, "Opening new source URL");
310                 } else {
311                     if (mLogVerbose) Log.v(TAG, "Opening new source Asset");
312                 }
313                 setupMediaPlayer(mSelectedIsUrl);
314             }
315         } else if (name.equals("volume")) {
316             if (isOpen()) {
317                 mMediaPlayer.setVolume(mVolume, mVolume);
318             }
319         }
320     }
321
322     synchronized public void pauseVideo(boolean pauseState) {
323         if (isOpen()) {
324             if (pauseState && !mPaused) {
325                 mMediaPlayer.pause();
326             } else if (!pauseState && mPaused) {
327                 mMediaPlayer.start();
328             }
329         }
330         mPaused = pauseState;
331     }
332
333     /** Creates a media player, sets it up, and calls prepare */
334     synchronized private boolean setupMediaPlayer(boolean useUrl) {
335         mPrepared = false;
336         mGotSize = false;
337         mPlaying = false;
338         mPaused = false;
339         mCompleted = false;
340         mNewFrameAvailable = false;
341
342         if (mLogVerbose) Log.v(TAG, "Setting up playback.");
343
344         if (mMediaPlayer != null) {
345             // Clean up existing media players
346             if (mLogVerbose) Log.v(TAG, "Resetting existing MediaPlayer.");
347             mMediaPlayer.reset();
348         } else {
349             // Create new media player
350             if (mLogVerbose) Log.v(TAG, "Creating new MediaPlayer.");
351             mMediaPlayer = new MediaPlayer();
352         }
353
354         if (mMediaPlayer == null) {
355             throw new RuntimeException("Unable to create a MediaPlayer!");
356         }
357
358         // Set up data sources, etc
359         try {
360             if (useUrl) {
361                 if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to URI " + mSourceUrl);
362                 mMediaPlayer.setDataSource(mSourceUrl);
363             } else {
364                 if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to asset " + mSourceAsset);
365                 mMediaPlayer.setDataSource(mSourceAsset.getFileDescriptor(), mSourceAsset.getStartOffset(), mSourceAsset.getLength());
366             }
367         } catch(IOException e) {
368             mMediaPlayer.release();
369             mMediaPlayer = null;
370             if (useUrl) {
371                 throw new RuntimeException(String.format("Unable to set MediaPlayer to URL %s!", mSourceUrl), e);
372             } else {
373                 throw new RuntimeException(String.format("Unable to set MediaPlayer to asset %s!", mSourceAsset), e);
374             }
375         } catch(IllegalArgumentException e) {
376             mMediaPlayer.release();
377             mMediaPlayer = null;
378             if (useUrl) {
379                 throw new RuntimeException(String.format("Unable to set MediaPlayer to URL %s!", mSourceUrl), e);
380             } else {
381                 throw new RuntimeException(String.format("Unable to set MediaPlayer to asset %s!", mSourceAsset), e);
382             }
383         }
384
385         mMediaPlayer.setLooping(mLooping);
386         mMediaPlayer.setVolume(mVolume, mVolume);
387
388         // Bind it to our media frame
389         Surface surface = new Surface(mSurfaceTexture);
390         mMediaPlayer.setSurface(surface);
391         surface.release();
392
393         // Connect Media Player to callbacks
394
395         mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener);
396         mMediaPlayer.setOnPreparedListener(onPreparedListener);
397         mMediaPlayer.setOnCompletionListener(onCompletionListener);
398
399         // Connect SurfaceTexture to callback
400         mSurfaceTexture.setOnFrameAvailableListener(onMediaFrameAvailableListener);
401
402         if (mLogVerbose) Log.v(TAG, "Preparing MediaPlayer.");
403         mMediaPlayer.prepareAsync();
404
405         return true;
406     }
407
408     private MediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener =
409             new MediaPlayer.OnVideoSizeChangedListener() {
410         public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
411             if (mLogVerbose) Log.v(TAG, "MediaPlayer sent dimensions: " + width + " x " + height);
412             if (!mGotSize) {
413                 mOutputFormat.setDimensions(width, height);
414             } else {
415                 if (mOutputFormat.getWidth() != width ||
416                     mOutputFormat.getHeight() != height) {
417                     Log.e(TAG, "Multiple video size change events received!");
418                 }
419             }
420             synchronized(MediaSource.this) {
421                 mGotSize = true;
422                 MediaSource.this.notify();
423             }
424         }
425     };
426
427     private MediaPlayer.OnPreparedListener onPreparedListener =
428             new MediaPlayer.OnPreparedListener() {
429         public void onPrepared(MediaPlayer mp) {
430             if (mLogVerbose) Log.v(TAG, "MediaPlayer is prepared");
431             synchronized(MediaSource.this) {
432                 mPrepared = true;
433                 MediaSource.this.notify();
434             }
435         }
436     };
437
438     private MediaPlayer.OnCompletionListener onCompletionListener =
439             new MediaPlayer.OnCompletionListener() {
440         public void onCompletion(MediaPlayer mp) {
441             if (mLogVerbose) Log.v(TAG, "MediaPlayer has completed playback");
442             synchronized(MediaSource.this) {
443                 mCompleted = true;
444             }
445         }
446     };
447
448     private SurfaceTexture.OnFrameAvailableListener onMediaFrameAvailableListener =
449             new SurfaceTexture.OnFrameAvailableListener() {
450         public void onFrameAvailable(SurfaceTexture surfaceTexture) {
451             if (mLogVerbose) Log.v(TAG, "New frame from media player");
452             synchronized(MediaSource.this) {
453                 if (mLogVerbose) Log.v(TAG, "New frame: notify");
454                 mNewFrameAvailable = true;
455                 MediaSource.this.notify();
456                 if (mLogVerbose) Log.v(TAG, "New frame: notify done");
457             }
458         }
459     };
460
461 }