1/*
2 * Copyright (C) 2014 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//#define LOG_NDEBUG 0
18#define LOG_TAG "mediafilterTest"
19
20#include <inttypes.h>
21
22#include <binder/ProcessState.h>
23#include <filters/ColorConvert.h>
24#include <gui/ISurfaceComposer.h>
25#include <gui/SurfaceComposerClient.h>
26#include <gui/Surface.h>
27#include <media/ICrypto.h>
28#include <media/IMediaHTTPService.h>
29#include <media/MediaCodecBuffer.h>
30#include <media/stagefright/foundation/ABuffer.h>
31#include <media/stagefright/foundation/ADebug.h>
32#include <media/stagefright/foundation/AMessage.h>
33#include <media/stagefright/MediaCodec.h>
34#include <media/stagefright/NuMediaExtractor.h>
35#include <media/stagefright/RenderScriptWrapper.h>
36#include <OMX_IVCommon.h>
37#include <ui/DisplayInfo.h>
38
39#include "RenderScript.h"
40#include "ScriptC_argbtorgba.h"
41#include "ScriptC_nightvision.h"
42#include "ScriptC_saturation.h"
43
44// test parameters
45static const bool kTestFlush = true;        // Note: true will drop 1 out of
46static const int kFlushAfterFrames = 25;    // kFlushAfterFrames output frames
47static const int64_t kTimeout = 500ll;
48
49// built-in filter parameters
50static const int32_t kInvert = false;   // ZeroFilter param
51static const float kBlurRadius = 15.0f; // IntrinsicBlurFilter param
52static const float kSaturation = 0.0f;  // SaturationFilter param
53
54static void usage(const char *me) {
55    fprintf(stderr, "usage: [flags] %s\n"
56                    "\t[-b] use IntrinsicBlurFilter\n"
57                    "\t[-c] use argb to rgba conversion RSFilter\n"
58                    "\t[-n] use night vision RSFilter\n"
59                    "\t[-r] use saturation RSFilter\n"
60                    "\t[-s] use SaturationFilter\n"
61                    "\t[-z] use ZeroFilter (copy filter)\n"
62                    "\t[-R] render output to surface (enables -S)\n"
63                    "\t[-S] allocate buffers from a surface\n"
64                    "\t[-T] use render timestamps (enables -R)\n",
65                    me);
66    exit(1);
67}
68
69namespace android {
70
71struct SaturationRSFilter : RenderScriptWrapper::RSFilterCallback {
72    void init(const RSC::sp<RSC::RS> &context) {
73        mScript = new ScriptC_saturation(context);
74        mScript->set_gSaturation(3.f);
75    }
76
77    virtual status_t processBuffers(
78            RSC::Allocation *inBuffer, RSC::Allocation *outBuffer) {
79        mScript->forEach_root(inBuffer, outBuffer);
80
81        return OK;
82    }
83
84    status_t handleSetParameters(const sp<AMessage> &msg __unused) {
85        return OK;
86    }
87
88private:
89    RSC::sp<ScriptC_saturation> mScript;
90};
91
92struct NightVisionRSFilter : RenderScriptWrapper::RSFilterCallback {
93    void init(const RSC::sp<RSC::RS> &context) {
94        mScript = new ScriptC_nightvision(context);
95    }
96
97    virtual status_t processBuffers(
98            RSC::Allocation *inBuffer, RSC::Allocation *outBuffer) {
99        mScript->forEach_root(inBuffer, outBuffer);
100
101        return OK;
102    }
103
104    status_t handleSetParameters(const sp<AMessage> &msg __unused) {
105        return OK;
106    }
107
108private:
109    RSC::sp<ScriptC_nightvision> mScript;
110};
111
112struct ARGBToRGBARSFilter : RenderScriptWrapper::RSFilterCallback {
113    void init(const RSC::sp<RSC::RS> &context) {
114        mScript = new ScriptC_argbtorgba(context);
115    }
116
117    virtual status_t processBuffers(
118            RSC::Allocation *inBuffer, RSC::Allocation *outBuffer) {
119        mScript->forEach_root(inBuffer, outBuffer);
120
121        return OK;
122    }
123
124    status_t handleSetParameters(const sp<AMessage> &msg __unused) {
125        return OK;
126    }
127
128private:
129    RSC::sp<ScriptC_argbtorgba> mScript;
130};
131
132struct CodecState {
133    sp<MediaCodec> mCodec;
134    Vector<sp<MediaCodecBuffer> > mInBuffers;
135    Vector<sp<MediaCodecBuffer> > mOutBuffers;
136    bool mSignalledInputEOS;
137    bool mSawOutputEOS;
138    int64_t mNumBuffersDecoded;
139};
140
141struct DecodedFrame {
142    size_t index;
143    size_t offset;
144    size_t size;
145    int64_t presentationTimeUs;
146    uint32_t flags;
147};
148
149enum FilterType {
150    FILTERTYPE_ZERO,
151    FILTERTYPE_INTRINSIC_BLUR,
152    FILTERTYPE_SATURATION,
153    FILTERTYPE_RS_SATURATION,
154    FILTERTYPE_RS_NIGHT_VISION,
155    FILTERTYPE_RS_ARGB_TO_RGBA,
156};
157
158size_t inputFramesSinceFlush = 0;
159void tryCopyDecodedBuffer(
160        List<DecodedFrame> *decodedFrameIndices,
161        CodecState *filterState,
162        CodecState *vidState) {
163    if (decodedFrameIndices->empty()) {
164        return;
165    }
166
167    size_t filterIndex;
168    status_t err = filterState->mCodec->dequeueInputBuffer(
169            &filterIndex, kTimeout);
170    if (err != OK) {
171        return;
172    }
173
174    ++inputFramesSinceFlush;
175
176    DecodedFrame frame = *decodedFrameIndices->begin();
177
178    // only consume a buffer if we are not going to flush, since we expect
179    // the dequeue -> flush -> queue operation to cause an error and
180    // not produce an output frame
181    if (!kTestFlush || inputFramesSinceFlush < kFlushAfterFrames) {
182        decodedFrameIndices->erase(decodedFrameIndices->begin());
183    }
184    size_t outIndex = frame.index;
185
186    const sp<MediaCodecBuffer> &srcBuffer =
187        vidState->mOutBuffers.itemAt(outIndex);
188    const sp<MediaCodecBuffer> &destBuffer =
189        filterState->mInBuffers.itemAt(filterIndex);
190
191    sp<AMessage> srcFormat, destFormat;
192    vidState->mCodec->getOutputFormat(&srcFormat);
193    filterState->mCodec->getInputFormat(&destFormat);
194
195    int32_t srcWidth, srcHeight, srcStride, srcSliceHeight;
196    int32_t srcColorFormat, destColorFormat;
197    int32_t destWidth, destHeight, destStride, destSliceHeight;
198    CHECK(srcFormat->findInt32("stride", &srcStride)
199            && srcFormat->findInt32("slice-height", &srcSliceHeight)
200            && srcFormat->findInt32("width", &srcWidth)
201            && srcFormat->findInt32("height", & srcHeight)
202            && srcFormat->findInt32("color-format", &srcColorFormat));
203    CHECK(destFormat->findInt32("stride", &destStride)
204            && destFormat->findInt32("slice-height", &destSliceHeight)
205            && destFormat->findInt32("width", &destWidth)
206            && destFormat->findInt32("height", & destHeight)
207            && destFormat->findInt32("color-format", &destColorFormat));
208
209    CHECK(srcWidth <= destStride && srcHeight <= destSliceHeight);
210
211    convertYUV420spToARGB(
212            srcBuffer->data(),
213            srcBuffer->data() + srcStride * srcSliceHeight,
214            srcWidth,
215            srcHeight,
216            destBuffer->data());
217
218    // copy timestamp
219    int64_t timeUs;
220    CHECK(srcBuffer->meta()->findInt64("timeUs", &timeUs));
221    destBuffer->meta()->setInt64("timeUs", timeUs);
222
223    if (kTestFlush && inputFramesSinceFlush >= kFlushAfterFrames) {
224        inputFramesSinceFlush = 0;
225
226        // check that queueing a buffer that was dequeued before flush
227        // fails with expected error EACCES
228        filterState->mCodec->flush();
229
230        err = filterState->mCodec->queueInputBuffer(
231                filterIndex, 0 /* offset */, destBuffer->size(),
232                timeUs, frame.flags);
233
234        if (err == OK) {
235            ALOGE("FAIL: queue after flush returned OK");
236        } else if (err != -EACCES) {
237            ALOGE("queueInputBuffer after flush returned %d, "
238                    "expected -EACCES (-13)", err);
239        }
240    } else {
241        err = filterState->mCodec->queueInputBuffer(
242                filterIndex, 0 /* offset */, destBuffer->size(),
243                timeUs, frame.flags);
244        CHECK(err == OK);
245
246        err = vidState->mCodec->releaseOutputBuffer(outIndex);
247        CHECK(err == OK);
248    }
249}
250
251size_t outputFramesSinceFlush = 0;
252void tryDrainOutputBuffer(
253        CodecState *filterState,
254        const sp<Surface> &surface, bool renderSurface,
255        bool useTimestamp, int64_t *startTimeRender) {
256    size_t index;
257    size_t offset;
258    size_t size;
259    int64_t presentationTimeUs;
260    uint32_t flags;
261    status_t err = filterState->mCodec->dequeueOutputBuffer(
262            &index, &offset, &size, &presentationTimeUs, &flags,
263            kTimeout);
264
265    if (err != OK) {
266        return;
267    }
268
269    ++outputFramesSinceFlush;
270
271    if (kTestFlush && outputFramesSinceFlush >= kFlushAfterFrames) {
272        filterState->mCodec->flush();
273    }
274
275    if (surface == NULL || !renderSurface) {
276        err = filterState->mCodec->releaseOutputBuffer(index);
277    } else if (useTimestamp) {
278        if (*startTimeRender == -1) {
279            // begin rendering 2 vsyncs after first decode
280            *startTimeRender = systemTime(SYSTEM_TIME_MONOTONIC)
281                    + 33000000 - (presentationTimeUs * 1000);
282        }
283        presentationTimeUs =
284                (presentationTimeUs * 1000) + *startTimeRender;
285        err = filterState->mCodec->renderOutputBufferAndRelease(
286                index, presentationTimeUs);
287    } else {
288        err = filterState->mCodec->renderOutputBufferAndRelease(index);
289    }
290
291    if (kTestFlush && outputFramesSinceFlush >= kFlushAfterFrames) {
292        outputFramesSinceFlush = 0;
293
294        // releasing the buffer dequeued before flush should cause an error
295        // if so, the frame will also be skipped in output stream
296        if (err == OK) {
297            ALOGE("FAIL: release after flush returned OK");
298        } else if (err != -EACCES) {
299            ALOGE("releaseOutputBuffer after flush returned %d, "
300                    "expected -EACCES (-13)", err);
301        }
302    } else {
303        CHECK(err == OK);
304    }
305
306    if (flags & MediaCodec::BUFFER_FLAG_EOS) {
307        ALOGV("reached EOS on output.");
308        filterState->mSawOutputEOS = true;
309    }
310}
311
312static int decode(
313        const sp<ALooper> &looper,
314        const char *path,
315        const sp<Surface> &surface,
316        bool renderSurface,
317        bool useTimestamp,
318        FilterType filterType) {
319
320    static int64_t kTimeout = 500ll;
321
322    sp<NuMediaExtractor> extractor = new NuMediaExtractor;
323    if (extractor->setDataSource(NULL /* httpService */, path) != OK) {
324        fprintf(stderr, "unable to instantiate extractor.\n");
325        return 1;
326    }
327
328    KeyedVector<size_t, CodecState> stateByTrack;
329
330    CodecState *vidState = NULL;
331    for (size_t i = 0; i < extractor->countTracks(); ++i) {
332        sp<AMessage> format;
333        status_t err = extractor->getTrackFormat(i, &format);
334        CHECK(err == OK);
335
336        AString mime;
337        CHECK(format->findString("mime", &mime));
338        bool isVideo = !strncasecmp(mime.c_str(), "video/", 6);
339        if (!isVideo) {
340            continue;
341        }
342
343        ALOGV("selecting track %zu", i);
344
345        err = extractor->selectTrack(i);
346        CHECK(err == OK);
347
348        CodecState *state =
349            &stateByTrack.editValueAt(stateByTrack.add(i, CodecState()));
350
351        vidState = state;
352
353        state->mNumBuffersDecoded = 0;
354
355        state->mCodec = MediaCodec::CreateByType(
356                looper, mime.c_str(), false /* encoder */);
357
358        CHECK(state->mCodec != NULL);
359
360        err = state->mCodec->configure(
361                format, NULL /* surface */, NULL /* crypto */, 0 /* flags */);
362
363        CHECK(err == OK);
364
365        state->mSignalledInputEOS = false;
366        state->mSawOutputEOS = false;
367
368        break;
369    }
370    CHECK(!stateByTrack.isEmpty());
371    CHECK(vidState != NULL);
372    sp<AMessage> vidFormat;
373    vidState->mCodec->getOutputFormat(&vidFormat);
374
375    // set filter to use ARGB8888
376    vidFormat->setInt32("color-format", OMX_COLOR_Format32bitARGB8888);
377    // set app cache directory path
378    vidFormat->setString("cacheDir", "/system/bin");
379
380    // create RenderScript context for RSFilters
381    RSC::sp<RSC::RS> context = new RSC::RS();
382    context->init("/system/bin");
383
384    sp<RenderScriptWrapper::RSFilterCallback> rsFilter;
385
386    // create renderscript wrapper for RSFilters
387    sp<RenderScriptWrapper> rsWrapper = new RenderScriptWrapper;
388    rsWrapper->mContext = context.get();
389
390    CodecState *filterState = new CodecState();
391    filterState->mNumBuffersDecoded = 0;
392
393    sp<AMessage> params = new AMessage();
394
395    switch (filterType) {
396        case FILTERTYPE_ZERO:
397        {
398            filterState->mCodec = MediaCodec::CreateByComponentName(
399                    looper, "android.filter.zerofilter");
400            params->setInt32("invert", kInvert);
401            break;
402        }
403        case FILTERTYPE_INTRINSIC_BLUR:
404        {
405            filterState->mCodec = MediaCodec::CreateByComponentName(
406                    looper, "android.filter.intrinsicblur");
407            params->setFloat("blur-radius", kBlurRadius);
408            break;
409        }
410        case FILTERTYPE_SATURATION:
411        {
412            filterState->mCodec = MediaCodec::CreateByComponentName(
413                    looper, "android.filter.saturation");
414            params->setFloat("saturation", kSaturation);
415            break;
416        }
417        case FILTERTYPE_RS_SATURATION:
418        {
419            SaturationRSFilter *satFilter = new SaturationRSFilter;
420            satFilter->init(context);
421            rsFilter = satFilter;
422            rsWrapper->mCallback = rsFilter;
423            vidFormat->setObject("rs-wrapper", rsWrapper);
424
425            filterState->mCodec = MediaCodec::CreateByComponentName(
426                    looper, "android.filter.RenderScript");
427            break;
428        }
429        case FILTERTYPE_RS_NIGHT_VISION:
430        {
431            NightVisionRSFilter *nightVisionFilter = new NightVisionRSFilter;
432            nightVisionFilter->init(context);
433            rsFilter = nightVisionFilter;
434            rsWrapper->mCallback = rsFilter;
435            vidFormat->setObject("rs-wrapper", rsWrapper);
436
437            filterState->mCodec = MediaCodec::CreateByComponentName(
438                    looper, "android.filter.RenderScript");
439            break;
440        }
441        case FILTERTYPE_RS_ARGB_TO_RGBA:
442        {
443            ARGBToRGBARSFilter *argbToRgbaFilter = new ARGBToRGBARSFilter;
444            argbToRgbaFilter->init(context);
445            rsFilter = argbToRgbaFilter;
446            rsWrapper->mCallback = rsFilter;
447            vidFormat->setObject("rs-wrapper", rsWrapper);
448
449            filterState->mCodec = MediaCodec::CreateByComponentName(
450                    looper, "android.filter.RenderScript");
451            break;
452        }
453        default:
454        {
455            LOG_ALWAYS_FATAL("mediacodec.cpp error: unrecognized FilterType");
456            break;
457        }
458    }
459    CHECK(filterState->mCodec != NULL);
460
461    status_t err = filterState->mCodec->configure(
462            vidFormat /* format */, surface, NULL /* crypto */, 0 /* flags */);
463    CHECK(err == OK);
464
465    filterState->mSignalledInputEOS = false;
466    filterState->mSawOutputEOS = false;
467
468    int64_t startTimeUs = ALooper::GetNowUs();
469    int64_t startTimeRender = -1;
470
471    for (size_t i = 0; i < stateByTrack.size(); ++i) {
472        CodecState *state = &stateByTrack.editValueAt(i);
473
474        sp<MediaCodec> codec = state->mCodec;
475
476        CHECK_EQ((status_t)OK, codec->start());
477
478        CHECK_EQ((status_t)OK, codec->getInputBuffers(&state->mInBuffers));
479        CHECK_EQ((status_t)OK, codec->getOutputBuffers(&state->mOutBuffers));
480
481        ALOGV("got %zu input and %zu output buffers",
482                state->mInBuffers.size(), state->mOutBuffers.size());
483    }
484
485    CHECK_EQ((status_t)OK, filterState->mCodec->setParameters(params));
486
487    if (kTestFlush) {
488        status_t flushErr = filterState->mCodec->flush();
489        if (flushErr == OK) {
490            ALOGE("FAIL: Flush before start returned OK");
491        } else {
492            ALOGV("Flush before start returned status %d, usually ENOSYS (-38)",
493                    flushErr);
494        }
495    }
496
497    CHECK_EQ((status_t)OK, filterState->mCodec->start());
498    CHECK_EQ((status_t)OK, filterState->mCodec->getInputBuffers(
499            &filterState->mInBuffers));
500    CHECK_EQ((status_t)OK, filterState->mCodec->getOutputBuffers(
501            &filterState->mOutBuffers));
502
503    if (kTestFlush) {
504        status_t flushErr = filterState->mCodec->flush();
505        if (flushErr != OK) {
506            ALOGE("FAIL: Flush after start returned %d, expect OK (0)",
507                    flushErr);
508        } else {
509            ALOGV("Flush immediately after start OK");
510        }
511    }
512
513    List<DecodedFrame> decodedFrameIndices;
514
515    // loop until decoder reaches EOS
516    bool sawInputEOS = false;
517    bool sawOutputEOSOnAllTracks = false;
518    while (!sawOutputEOSOnAllTracks) {
519        if (!sawInputEOS) {
520            size_t trackIndex;
521            status_t err = extractor->getSampleTrackIndex(&trackIndex);
522
523            if (err != OK) {
524                ALOGV("saw input eos");
525                sawInputEOS = true;
526            } else {
527                CodecState *state = &stateByTrack.editValueFor(trackIndex);
528
529                size_t index;
530                err = state->mCodec->dequeueInputBuffer(&index, kTimeout);
531
532                if (err == OK) {
533                    ALOGV("filling input buffer %zu", index);
534
535                    const sp<MediaCodecBuffer> &buffer = state->mInBuffers.itemAt(index);
536                    sp<ABuffer> abuffer = new ABuffer(buffer->base(), buffer->capacity());
537
538                    err = extractor->readSampleData(abuffer);
539                    CHECK(err == OK);
540                    buffer->setRange(abuffer->offset(), abuffer->size());
541
542                    int64_t timeUs;
543                    err = extractor->getSampleTime(&timeUs);
544                    CHECK(err == OK);
545
546                    uint32_t bufferFlags = 0;
547
548                    err = state->mCodec->queueInputBuffer(
549                            index, 0 /* offset */, buffer->size(),
550                            timeUs, bufferFlags);
551
552                    CHECK(err == OK);
553
554                    extractor->advance();
555                } else {
556                    CHECK_EQ(err, -EAGAIN);
557                }
558            }
559        } else {
560            for (size_t i = 0; i < stateByTrack.size(); ++i) {
561                CodecState *state = &stateByTrack.editValueAt(i);
562
563                if (!state->mSignalledInputEOS) {
564                    size_t index;
565                    status_t err =
566                        state->mCodec->dequeueInputBuffer(&index, kTimeout);
567
568                    if (err == OK) {
569                        ALOGV("signalling input EOS on track %zu", i);
570
571                        err = state->mCodec->queueInputBuffer(
572                                index, 0 /* offset */, 0 /* size */,
573                                0ll /* timeUs */, MediaCodec::BUFFER_FLAG_EOS);
574
575                        CHECK(err == OK);
576
577                        state->mSignalledInputEOS = true;
578                    } else {
579                        CHECK_EQ(err, -EAGAIN);
580                    }
581                }
582            }
583        }
584
585        sawOutputEOSOnAllTracks = true;
586        for (size_t i = 0; i < stateByTrack.size(); ++i) {
587            CodecState *state = &stateByTrack.editValueAt(i);
588
589            if (state->mSawOutputEOS) {
590                continue;
591            } else {
592                sawOutputEOSOnAllTracks = false;
593            }
594
595            DecodedFrame frame;
596            status_t err = state->mCodec->dequeueOutputBuffer(
597                    &frame.index, &frame.offset, &frame.size,
598                    &frame.presentationTimeUs, &frame.flags, kTimeout);
599
600            if (err == OK) {
601                ALOGV("draining decoded buffer %zu, time = %lld us",
602                        frame.index, (long long)frame.presentationTimeUs);
603
604                ++(state->mNumBuffersDecoded);
605
606                decodedFrameIndices.push_back(frame);
607
608                if (frame.flags & MediaCodec::BUFFER_FLAG_EOS) {
609                    ALOGV("reached EOS on decoder output.");
610                    state->mSawOutputEOS = true;
611                }
612
613            } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) {
614                ALOGV("INFO_OUTPUT_BUFFERS_CHANGED");
615                CHECK_EQ((status_t)OK, state->mCodec->getOutputBuffers(
616                        &state->mOutBuffers));
617
618                ALOGV("got %zu output buffers", state->mOutBuffers.size());
619            } else if (err == INFO_FORMAT_CHANGED) {
620                sp<AMessage> format;
621                CHECK_EQ((status_t)OK, state->mCodec->getOutputFormat(&format));
622
623                ALOGV("INFO_FORMAT_CHANGED: %s",
624                        format->debugString().c_str());
625            } else {
626                CHECK_EQ(err, -EAGAIN);
627            }
628
629            tryCopyDecodedBuffer(&decodedFrameIndices, filterState, vidState);
630
631            tryDrainOutputBuffer(
632                    filterState, surface, renderSurface,
633                    useTimestamp, &startTimeRender);
634        }
635    }
636
637    // after EOS on decoder, let filter reach EOS
638    while (!filterState->mSawOutputEOS) {
639        tryCopyDecodedBuffer(&decodedFrameIndices, filterState, vidState);
640
641        tryDrainOutputBuffer(
642                filterState, surface, renderSurface,
643                useTimestamp, &startTimeRender);
644    }
645
646    int64_t elapsedTimeUs = ALooper::GetNowUs() - startTimeUs;
647
648    for (size_t i = 0; i < stateByTrack.size(); ++i) {
649        CodecState *state = &stateByTrack.editValueAt(i);
650
651        CHECK_EQ((status_t)OK, state->mCodec->release());
652
653        printf("track %zu: %" PRId64 " frames decoded and filtered, "
654                "%.2f fps.\n", i, state->mNumBuffersDecoded,
655                state->mNumBuffersDecoded * 1E6 / elapsedTimeUs);
656    }
657
658    return 0;
659}
660
661}  // namespace android
662
663int main(int argc, char **argv) {
664    using namespace android;
665
666    const char *me = argv[0];
667
668    bool useSurface = false;
669    bool renderSurface = false;
670    bool useTimestamp = false;
671    FilterType filterType = FILTERTYPE_ZERO;
672
673    int res;
674    while ((res = getopt(argc, argv, "bcnrszTRSh")) >= 0) {
675        switch (res) {
676            case 'b':
677            {
678                filterType = FILTERTYPE_INTRINSIC_BLUR;
679                break;
680            }
681            case 'c':
682            {
683                filterType = FILTERTYPE_RS_ARGB_TO_RGBA;
684                break;
685            }
686            case 'n':
687            {
688                filterType = FILTERTYPE_RS_NIGHT_VISION;
689                break;
690            }
691            case 'r':
692            {
693                filterType = FILTERTYPE_RS_SATURATION;
694                break;
695            }
696            case 's':
697            {
698                filterType = FILTERTYPE_SATURATION;
699                break;
700            }
701            case 'z':
702            {
703                filterType = FILTERTYPE_ZERO;
704                break;
705            }
706            case 'T':
707            {
708                useTimestamp = true;
709            }
710            // fall through
711            case 'R':
712            {
713                renderSurface = true;
714            }
715            // fall through
716            case 'S':
717            {
718                useSurface = true;
719                break;
720            }
721            case '?':
722            case 'h':
723            default:
724            {
725                usage(me);
726                break;
727            }
728        }
729    }
730
731    argc -= optind;
732    argv += optind;
733
734    if (argc != 1) {
735        usage(me);
736    }
737
738    ProcessState::self()->startThreadPool();
739
740    android::sp<ALooper> looper = new ALooper;
741    looper->start();
742
743    android::sp<SurfaceComposerClient> composerClient;
744    android::sp<SurfaceControl> control;
745    android::sp<Surface> surface;
746
747    if (useSurface) {
748        composerClient = new SurfaceComposerClient;
749        CHECK_EQ((status_t)OK, composerClient->initCheck());
750
751        android::sp<IBinder> display(SurfaceComposerClient::getBuiltInDisplay(
752                ISurfaceComposer::eDisplayIdMain));
753        DisplayInfo info;
754        SurfaceComposerClient::getDisplayInfo(display, &info);
755        ssize_t displayWidth = info.w;
756        ssize_t displayHeight = info.h;
757
758        ALOGV("display is %zd x %zd", displayWidth, displayHeight);
759
760        control = composerClient->createSurface(
761                String8("A Surface"), displayWidth, displayHeight,
762                PIXEL_FORMAT_RGBA_8888, 0);
763
764        CHECK(control != NULL);
765        CHECK(control->isValid());
766
767        SurfaceComposerClient::openGlobalTransaction();
768        CHECK_EQ((status_t)OK, control->setLayer(INT_MAX));
769        CHECK_EQ((status_t)OK, control->show());
770        SurfaceComposerClient::closeGlobalTransaction();
771
772        surface = control->getSurface();
773        CHECK(surface != NULL);
774    }
775
776    decode(looper, argv[0], surface, renderSurface, useTimestamp, filterType);
777
778    if (useSurface) {
779        composerClient->dispose();
780    }
781
782    looper->stop();
783
784    return 0;
785}
786