Skip to content

Commit 294f1e2

Browse files
committed
Handle content-generation failures better
If we somehow got a software codec -- maybe we're on an emulator -- the creation of the generated videos would fail. This killed the app shortly after it started, leaving an obscure failure in the log file. We now trap the failure and put up an exception dialog warning that not all features may be available. If it looks like we're trying to use the software AVC codec, we make that explicit in the error dialog.
1 parent de193ee commit 294f1e2

File tree

5 files changed

+82
-15
lines changed

5 files changed

+82
-15
lines changed

AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
1919
package="com.android.grafika"
20-
android:versionCode="21"
20+
android:versionCode="22"
2121
android:versionName="1.0" >
2222

2323
<uses-sdk

README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,14 @@ There are two areas where some amount of care is taken:
4141

4242
All code is written in the Java programming language -- the NDK is not used.
4343

44+
The first time Grafika starts, two videos are generated (gen-eight-rects, gen-sliders).
45+
If you want to experiment with the generation code, you can cause them to be re-generated
46+
from the main activity menu ("Regenerate content").
47+
4448
Some features of Grafika may not work on an emulator, notably anything that involves
4549
encoding video. The problem is that the software AVC encoder doesn't support
4650
`createInputSurface()`, and all of Grafika's video encoding features currently make
47-
use of that. \[Grafika currently crashes on startup when it tries to generate a
48-
couple of videos; this will be fixed.]
51+
use of that. Very little works in the AOSP emulator, even with `-gpu on`.
4952

5053

5154
Current features
@@ -72,6 +75,7 @@ Current features
7275
usually matches what the camera provides.
7376

7477
[Double decode](src/com/android/grafika/DoubleDecodeActivity.java). Decodes two video streams side-by-side to a pair of `TextureViews`.
78+
- Plays the two auto-generated videos. Note they play at different rates.
7579
- The video decoders don't stop when the screen is rotated. We retain the `SurfaceTexture`
7680
and just attach it to the new `TextureView`. Useful for avoiding expensive codec reconfigures.
7781
The decoders *do* stop if you leave the activity, so we don't tie up hardware codec
@@ -145,11 +149,6 @@ Feature & fix ideas
145149

146150
In no particular order.
147151

148-
- Try to detect when the codec selects a software AVC codec, as the current SoftAVC
149-
codec does not work with input surfaces.
150-
- If initial movie generation fails, handle it better (currently crashes -- should
151-
show a dialog explaining what happened). This currently prevents Grafika from
152-
working at all on emulators.
153152
- Add a trivial glTexImage2D texture upload speed benchmark (maybe 512x512 RGBA).
154153
- Update MoviePlayer#doExtract() to improve startup latency
155154
(http://stackoverflow.com/questions/21440820/).

res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
<string name="about_text">\nA random assortment of graphics and media hacks.\n\n
2929
[This is a perpetual work in progress.]\n\n
3030
Copyright 2013-2014 Google Inc. All rights reserved.</string>
31+
<string name="content_generation_failed_title">Unable to generate content</string>
32+
<string name="content_generation_failed_msg">Failed to generate content. Some
33+
features may be unavailable.\n\n%1$s</string>
3134
<string name="ok">OK</string>
3235
<string name="cancel">Cancel</string>
3336
<string name="save">Save</string>

src/com/android/grafika/ContentManager.java

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.app.Activity;
2020
import android.app.AlertDialog;
2121
import android.content.Context;
22+
import android.content.DialogInterface;
2223
import android.os.AsyncTask;
2324
import android.util.Log;
2425
import android.widget.ProgressBar;
@@ -88,7 +89,7 @@ public static void initialize(Context context) {
8889
* <p>
8990
* If this returns false, call createAll.
9091
*/
91-
public boolean isContentCreated(Context unused) {
92+
public boolean isContentCreated(@SuppressWarnings("unused") Context unused) {
9293
// Ideally this would probe each individual item to see if anything needs to be done,
9394
// and a subsequent "prepare" call would generate only the necessary items. This
9495
// takes a much simpler approach and just checks to see if the files exist. If the
@@ -117,7 +118,8 @@ public void createAll(Activity caller) {
117118
* Prepares the specified content. For example, if the caller requires a movie that doesn't
118119
* exist, this will post a progress dialog and generate the movie.
119120
* <p>
120-
* Call from main UI thread.
121+
* Call from main UI thread. This returns immediately. Content generation continues
122+
* on a background thread.
121123
*/
122124
public void prepareContent(Activity caller, int[] tags) {
123125
// Put up the progress dialog.
@@ -126,7 +128,7 @@ public void prepareContent(Activity caller, int[] tags) {
126128
AlertDialog dialog = builder.show();
127129

128130
// Generate content in async task.
129-
GenerateTask genTask = new GenerateTask(dialog, tags);
131+
GenerateTask genTask = new GenerateTask(caller, dialog, tags);
130132
genTask.execute();
131133
}
132134

@@ -201,6 +203,7 @@ public interface ProgressUpdater {
201203
private static class GenerateTask extends AsyncTask<Void, Integer, Integer>
202204
implements ProgressUpdater {
203205
// ----- accessed from UI thread -----
206+
private final Context mContext;
204207
private final AlertDialog mPrepDialog;
205208
private final ProgressBar mProgressBar;
206209

@@ -209,9 +212,11 @@ private static class GenerateTask extends AsyncTask<Void, Integer, Integer>
209212

210213
// ----- accessed from both -----
211214
private final int[] mTags;
215+
private volatile RuntimeException mFailure;
212216

213217

214-
public GenerateTask(AlertDialog dialog, int[] tags) {
218+
public GenerateTask(Context context, AlertDialog dialog, int[] tags) {
219+
mContext = context;
215220
mPrepDialog = dialog;
216221
mTags = tags;
217222
mProgressBar = (ProgressBar) mPrepDialog.findViewById(R.id.work_progress);
@@ -226,10 +231,20 @@ protected Integer doInBackground(Void... params) {
226231
for (int i = 0; i < mTags.length; i++) {
227232
mCurrentIndex = i;
228233
updateProgress(0);
229-
contentManager.prepare(this, mTags[i]);
234+
try {
235+
contentManager.prepare(this, mTags[i]);
236+
} catch (RuntimeException re) {
237+
mFailure = re;
238+
break;
239+
}
230240
updateProgress(100);
231241
}
232-
Log.d(TAG, "done");
242+
243+
if (mFailure != null) {
244+
Log.w(TAG, "Failed while generating content", mFailure);
245+
} else {
246+
Log.d(TAG, "generation complete");
247+
}
233248
return 0;
234249
}
235250

@@ -254,6 +269,30 @@ protected void onProgressUpdate(Integer... progressArray) {
254269
protected void onPostExecute(Integer result) {
255270
Log.d(TAG, "onPostExecute -- dismss");
256271
mPrepDialog.dismiss();
272+
273+
if (mFailure != null) {
274+
showFailureDialog(mContext, mFailure);
275+
}
276+
}
277+
278+
/**
279+
* Posts an error dialog, including the message from the failure exception.
280+
*/
281+
private void showFailureDialog(Context context, RuntimeException failure) {
282+
AlertDialog.Builder builder = new AlertDialog.Builder(context);
283+
builder.setTitle(R.string.content_generation_failed_title);
284+
String msg = context.getString(R.string.content_generation_failed_msg,
285+
failure.getMessage());
286+
builder.setMessage(msg);
287+
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
288+
@Override
289+
public void onClick(DialogInterface dialog, int id) {
290+
dialog.dismiss();
291+
}
292+
});
293+
builder.setCancelable(false);
294+
AlertDialog dialog = builder.create();
295+
dialog.show();
257296
}
258297
}
259298
}

src/com/android/grafika/GeneratedMovie.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import android.media.MediaFormat;
2222
import android.media.MediaMuxer;
2323
import android.util.Log;
24+
import android.view.Surface;
2425

2526
import com.android.grafika.gles.EglCore;
2627
import com.android.grafika.gles.WindowSurface;
@@ -56,6 +57,15 @@ public abstract class GeneratedMovie implements Content {
5657
*/
5758
public abstract void create(File outputFile, ContentManager.ProgressUpdater prog);
5859

60+
/**
61+
* Returns true if the codec has a software implementation.
62+
*/
63+
private static boolean isSoftwareCodec(MediaCodec codec) {
64+
String codecName = codec.getCodecInfo().getName();
65+
66+
return ("OMX.google.h264.encoder".equals(codecName));
67+
}
68+
5969
/**
6070
* Prepares the video encoder, muxer, and an EGL input surface.
6171
*/
@@ -78,8 +88,24 @@ protected void prepareEncoder(String mimeType, int width, int height, int bitRat
7888
// we can use for input and wrap it with a class that handles the EGL work.
7989
mEncoder = MediaCodec.createEncoderByType(mimeType);
8090
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
91+
Log.v(TAG, "encoder is " + mEncoder.getCodecInfo().getName());
92+
Surface surface;
93+
try {
94+
surface = mEncoder.createInputSurface();
95+
} catch (IllegalStateException ise) {
96+
// This is generally the first time we ever try to encode something through a
97+
// Surface, so specialize the message a bit if we can guess at why it's failing.
98+
// TODO: failure message should come out of strings.xml for i18n
99+
if (isSoftwareCodec(mEncoder)) {
100+
throw new RuntimeException("Can't use input surface with software codec: " +
101+
mEncoder.getCodecInfo().getName(),
102+
ise);
103+
} else {
104+
throw new RuntimeException("Failed to create input surface", ise);
105+
}
106+
}
81107
mEglCore = new EglCore(null, EglCore.FLAG_RECORDABLE);
82-
mInputSurface = new WindowSurface(mEglCore, mEncoder.createInputSurface(), true);
108+
mInputSurface = new WindowSurface(mEglCore, surface, true);
83109
mInputSurface.makeCurrent();
84110
mEncoder.start();
85111

0 commit comments

Comments
 (0)