/*
 * Runs the MediaCodec encoder, sending the output to the MediaMuxer.  The
 * input frames are coming from the virtual display as fast as SurfaceFlinger
 * wants to send them.
 *
 * The muxer must *not* have been started before calling.
 */
static status_t runEncoder(const sp<MediaCodec>& encoder,
        const sp<MediaMuxer>& muxer) {
    static int kTimeout = 250000;   // be responsive on signal
    status_t err;
    ssize_t trackIdx = -1;
    uint32_t debugNumFrames = 0;
    int64_t startWhenNsec = systemTime(CLOCK_MONOTONIC);
    int64_t endWhenNsec = startWhenNsec + seconds_to_nanoseconds(gTimeLimitSec);

    Vector<sp<ABuffer> > buffers;
    err = encoder->getOutputBuffers(&buffers);
    if (err != NO_ERROR) {
        fprintf(stderr, "Unable to get output buffers (err=%d)\n", err);
        return err;
    }

    // This is set by the signal handler.
    gStopRequested = false;

    // Run until we're signaled.
    while (!gStopRequested) {
        size_t bufIndex, offset, size;
        int64_t ptsUsec;
        uint32_t flags;

        if (systemTime(CLOCK_MONOTONIC) > endWhenNsec) {
            if (gVerbose) {
                printf("Time limit reached\n");
            }
            break;
        }

        ALOGV("Calling dequeueOutputBuffer");
        err = encoder->dequeueOutputBuffer(&bufIndex, &offset, &size, &ptsUsec,
                &flags, kTimeout);
        ALOGV("dequeueOutputBuffer returned %d", err);
        switch (err) {
        case NO_ERROR:
            // got a buffer
            if ((flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) != 0) {
                // ignore this -- we passed the CSD into MediaMuxer when
                // we got the format change notification
                ALOGV("Got codec config buffer (%u bytes); ignoring", size);
                size = 0;
            }
            if (size != 0) {
                ALOGV("Got data in buffer %d, size=%d, pts=%lld",
                        bufIndex, size, ptsUsec);
                CHECK(trackIdx != -1);

                // If the virtual display isn't providing us with timestamps,
                // use the current time.
                if (ptsUsec == 0) {
                    ptsUsec = systemTime(SYSTEM_TIME_MONOTONIC) / 1000;
                }

                // The MediaMuxer docs are unclear, but it appears that we
                // need to pass either the full set of BufferInfo flags, or
                // (flags & BUFFER_FLAG_SYNCFRAME).
                err = muxer->writeSampleData(buffers[bufIndex], trackIdx,
                        ptsUsec, flags);
                if (err != NO_ERROR) {
                    fprintf(stderr, "Failed writing data to muxer (err=%d)\n",
                            err);
                    return err;
                }
                debugNumFrames++;
            }
            err = encoder->releaseOutputBuffer(bufIndex);
            if (err != NO_ERROR) {
                fprintf(stderr, "Unable to release output buffer (err=%d)\n",
                        err);
                return err;
            }
            if ((flags & MediaCodec::BUFFER_FLAG_EOS) != 0) {
                // Not expecting EOS from SurfaceFlinger.  Go with it.
                ALOGD("Received end-of-stream");
                gStopRequested = false;
            }
            break;
        case -EAGAIN:                       // INFO_TRY_AGAIN_LATER
            ALOGV("Got -EAGAIN, looping");
            break;
        case INFO_FORMAT_CHANGED:           // INFO_OUTPUT_FORMAT_CHANGED
            {
                // format includes CSD, which we must provide to muxer
                ALOGV("Encoder format changed");
                sp<AMessage> newFormat;
                encoder->getOutputFormat(&newFormat);
                trackIdx = muxer->addTrack(newFormat);
                ALOGV("Starting muxer");
                err = muxer->start();
                if (err != NO_ERROR) {
                    fprintf(stderr, "Unable to start muxer (err=%d)\n", err);
                    return err;
                }
            }
            break;
        case INFO_OUTPUT_BUFFERS_CHANGED:   // INFO_OUTPUT_BUFFERS_CHANGED
            // not expected for an encoder; handle it anyway
            ALOGV("Encoder buffers changed");
            err = encoder->getOutputBuffers(&buffers);
            if (err != NO_ERROR) {
                fprintf(stderr,
                        "Unable to get new output buffers (err=%d)\n", err);
                return err;
            }
            break;
        case INVALID_OPERATION:
            fprintf(stderr, "Request for encoder buffer failed\n");
            return err;
        default:
            fprintf(stderr,
                    "Got weird result %d from dequeueOutputBuffer\n", err);
            return err;
        }
    }

    ALOGV("Encoder stopping (req=%d)", gStopRequested);
    if (gVerbose) {
        printf("Encoder stopping; recorded %u frames in %lld seconds\n",
                debugNumFrames,
                nanoseconds_to_seconds(systemTime(CLOCK_MONOTONIC) - startWhenNsec));
    }
    return NO_ERROR;
}
/*
 * Runs the MediaCodec encoder, sending the output to the MediaMuxer.  The
 * input frames are coming from the virtual display as fast as SurfaceFlinger
 * wants to send them.
 *
 * The muxer must *not* have been started before calling.
 */
static status_t runEncoder(const sp<MediaCodec>& audioEncoder,
        const sp<MediaCodec>& videoEncoder,
        const sp<MediaMuxer>& muxer) {
    static int kTimeout = 5000;   // be responsive on signal
    status_t err;
    ssize_t videoTrackIdx = -1;
    ssize_t audioTrackIdx = -1;
    uint32_t debugNumFrames = 0;
    int64_t startWhenNsec = systemTime(CLOCK_MONOTONIC);
    int64_t endWhenNsec = startWhenNsec + seconds_to_nanoseconds(gTimeLimitSec);
    uint32_t tracksAdded = 0;
    int64_t lastAudioPtsUs = 0;

    Vector<sp<ABuffer> > buffers;
    err = videoEncoder->getOutputBuffers(&buffers);
    if (err != NO_ERROR) {
        fprintf(stderr, "Unable to get output buffers (err=%d)\n", err);
        return err;
    }

    Vector<sp<ABuffer> > audioOutputBuffers;
    Vector<sp<ABuffer> > audioInputBuffers;
    sp<AudioRecord> audioRecorder;
    if (gRecordAudio) {
        err = audioEncoder->getOutputBuffers(&audioOutputBuffers);
        if (err != NO_ERROR) {
            fprintf(stderr, "Unable to get output audio buffers (err=%d)\n", err);
            return err;
        }

        err = audioEncoder->getInputBuffers(&audioInputBuffers);
        if (err != NO_ERROR) {
            fprintf(stderr, "Unable to get input audio buffers (err=%d)\n", err);
            return err;
        }

        // setup AudioRecord so we can source audio data to the audio codec
        audioRecorder = new AudioRecord();
        size_t minBuffSize = getMinBuffSize(kAudioSampleRate, 1, AUDIO_FORMAT_PCM_16_BIT);
        size_t buffSize = kSamplesPerFrame * 10;
        if (buffSize < minBuffSize) {
            buffSize = ((minBuffSize / kSamplesPerFrame) + 1) * kSamplesPerFrame * 2;
        }
        audioRecorder->set(
            (audio_source_t) 1,
            kAudioSampleRate,
            AUDIO_FORMAT_PCM_16_BIT,        // byte length, PCM
            (audio_channel_mask_t) 16,
            buffSize / 2,
            NULL,// callback_t
            NULL,// void* user
            0,             // notificationFrames,
            false,         // threadCanCallJava
            0);

        err = audioRecorder->initCheck();
        if (err != NO_ERROR) {
            fprintf(stderr,
                "Error creating AudioRecord instance: initialization check failed (err=%d)\n", err);
            return err;
        }
        audioRecorder->start();
    }

    // This is set by the signal handler.
    gStopRequested = false;

    jni_onRecordingStarted();
    // Run until we're signaled.
    while (!gStopRequested) {
        size_t bufIndex, offset, size;
        int64_t ptsUsec;
        uint32_t flags;

        if (systemTime(CLOCK_MONOTONIC) > endWhenNsec) {
            if (gVerbose) {
                printf("Time limit reached\n");
            }
            break;
        }

        // first lets send some audio off to the audio encoder if enabled
        if (gRecordAudio) {
            err = audioEncoder->dequeueInputBuffer(&bufIndex, kTimeout);
            if (err == NO_ERROR) {
                ssize_t audioSize = audioRecorder->read(audioInputBuffers[bufIndex]->data(), kSamplesPerFrame);
                err = audioEncoder->queueInputBuffer(
                        bufIndex,
                        0,
                        audioSize,
                        systemTime(CLOCK_MONOTONIC) / 1000,
                        0);
                ALOGV("Queued %d bytes of audio", audioSize);
            }
        }

        ALOGV("Calling dequeueOutputBuffer");
        err = videoEncoder->dequeueOutputBuffer(&bufIndex, &offset, &size, &ptsUsec,
                &flags, kTimeout);
        ALOGV("dequeueOutputBuffer returned %d", err);
        switch (err) {
        case NO_ERROR:
            // got a buffer
            if ((flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) != 0) {
                // ignore this -- we passed the CSD into MediaMuxer when
                // we got the format change notification
                ALOGV("Got codec config buffer (%u bytes); ignoring", size);
                size = 0;
            }
            if (size != 0) {
                ALOGV("Got data in video buffer %d, size=%d, pts=%lld",
                        bufIndex, size, ptsUsec);
                CHECK(videoTrackIdx != -1);

                // If the virtual display isn't providing us with timestamps,
                // use the current time.
                if (ptsUsec == 0) {
                    ptsUsec = systemTime(SYSTEM_TIME_MONOTONIC) / 1000;
                }

                // The MediaMuxer docs are unclear, but it appears that we
                // need to pass either the full set of BufferInfo flags, or
                // (flags & BUFFER_FLAG_SYNCFRAME).
                err = muxer->writeSampleData(buffers[bufIndex], videoTrackIdx,
                        ptsUsec, flags);
                if (err != NO_ERROR) {
                    fprintf(stderr, "Failed writing data to muxer (err=%d)\n",
                            err);
                    return err;
                }
                debugNumFrames++;
            }
            err = videoEncoder->releaseOutputBuffer(bufIndex);
            if (err != NO_ERROR) {
                fprintf(stderr, "Unable to release output buffer (err=%d)\n",
                        err);
                return err;
            }
            if ((flags & MediaCodec::BUFFER_FLAG_EOS) != 0) {
                // Not expecting EOS from SurfaceFlinger.  Go with it.
                ALOGV("Received end-of-stream");
                gStopRequested = false;
            }
            break;
        case -EAGAIN:                       // INFO_TRY_AGAIN_LATER
            ALOGV("Got -EAGAIN, looping");
            break;
        case INFO_FORMAT_CHANGED:           // INFO_OUTPUT_FORMAT_CHANGED
            {
                // format includes CSD, which we must provide to muxer
                ALOGV("Encoder format changed");
                sp<AMessage> newFormat;
                videoEncoder->getOutputFormat(&newFormat);
                videoTrackIdx = muxer->addTrack(newFormat);
                if (++tracksAdded >= (gRecordAudio ? 2 : 1)) {
                    ALOGV("Starting muxer");
                    err = muxer->start();
                    if (err != NO_ERROR) {
                        fprintf(stderr, "Unable to start muxer (err=%d)\n", err);
                        return err;
                    }
                }
            }
            break;
        case INFO_OUTPUT_BUFFERS_CHANGED:   // INFO_OUTPUT_BUFFERS_CHANGED
            // not expected for an encoder; handle it anyway
            ALOGV("Encoder buffers changed");
            err = videoEncoder->getOutputBuffers(&buffers);
            if (err != NO_ERROR) {
                fprintf(stderr,
                        "Unable to get new output buffers (err=%d)\n", err);
                return err;
            }
            break;
        case INVALID_OPERATION:
            fprintf(stderr, "Request for encoder buffer failed\n");
            return err;
        default:
            fprintf(stderr,
                    "Got weird result %d from dequeueOutputBuffer\n", err);
            return err;
        }

        if (gRecordAudio) {
            ALOGV("Calling dequeueOutputBuffer for audioEncoder");
            err = audioEncoder->dequeueOutputBuffer(&bufIndex, &offset, &size, &ptsUsec,
                    &flags, kTimeout);
            ALOGV("dequeueOutputBuffer returned %d", err);
            switch (err) {
            case NO_ERROR:
                // got a buffer
                if ((flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) != 0) {
                    // ignore this -- we passed the CSD into MediaMuxer when
                    // we got the format change notification
                    ALOGV("Got codec config buffer (%u bytes); ignoring", size);
                    size = 0;
                }
                if (size != 0) {
                    ALOGV("Got data in audio buffer %d, offset=%d, size=%d, pts=%lld",
                            bufIndex, offset, size, ptsUsec);
                    CHECK(audioTrackIdx != -1);

                    if (ptsUsec < 0) ptsUsec = 0;
                    if (ptsUsec < lastAudioPtsUs)
                        ptsUsec = lastAudioPtsUs + 23219; // magical AAC encoded frame time
                    lastAudioPtsUs = ptsUsec;

                    // The MediaMuxer docs are unclear, but it appears that we
                    // need to pass either the full set of BufferInfo flags, or
                    // (flags & BUFFER_FLAG_SYNCFRAME).
                    err = muxer->writeSampleData(audioOutputBuffers[bufIndex], audioTrackIdx,
                            ptsUsec, flags);
                    if (err != NO_ERROR) {
                        fprintf(stderr, "Failed writing data to muxer (err=%d)\n",
                                err);
                        return err;
                    }
                }
                err = audioEncoder->releaseOutputBuffer(bufIndex);
                if (err != NO_ERROR) {
                    fprintf(stderr, "Unable to release output buffer (err=%d)\n",
                            err);
                    return err;
                }
                if ((flags & MediaCodec::BUFFER_FLAG_EOS) != 0) {
                    // Not expecting EOS.  Go with it.
                    ALOGV("Received end-of-stream");
                    gStopRequested = false;
                }
                break;
            case -EAGAIN:                       // INFO_TRY_AGAIN_LATER
                ALOGV("Got -EAGAIN, looping");
                break;
            case INFO_FORMAT_CHANGED:           // INFO_OUTPUT_FORMAT_CHANGED
                {
                    // format includes CSD, which we must provide to muxer
                    ALOGV("Audio encoder format changed");
                    sp<AMessage> newFormat;
                    audioEncoder->getOutputFormat(&newFormat);
                    audioTrackIdx = muxer->addTrack(newFormat);
                    if (++tracksAdded >= 2) {
                        ALOGV("Starting muxer");
                        err = muxer->start();
                        if (err != NO_ERROR) {
                            fprintf(stderr, "Unable to start muxer (err=%d)\n", err);
                            return err;
                        }
                    }
                }
                break;
            case INFO_OUTPUT_BUFFERS_CHANGED:   // INFO_OUTPUT_BUFFERS_CHANGED
                // not expected for an encoder; handle it anyway
                ALOGV("Audio encoder buffers changed");
                err = audioEncoder->getOutputBuffers(&audioOutputBuffers);
                if (err != NO_ERROR) {
                    fprintf(stderr,
                            "Unable to get new output buffers (err=%d)\n", err);
                    return err;
                }
                break;
            case INVALID_OPERATION:
                fprintf(stderr, "Request for encoder buffer failed\n");
                return err;
            default:
                fprintf(stderr,
                        "Got weird result %d from dequeueOutputBuffer\n", err);
                return err;
            }
        }
    }

    if (gRecordAudio) audioRecorder->stop();

    ALOGV("Encoder stopping (req=%d)", gStopRequested);
    if (gVerbose) {
        printf("Encoder stopping; recorded %u frames in %lld seconds\n",
                debugNumFrames,
                nanoseconds_to_seconds(systemTime(CLOCK_MONOTONIC) - startWhenNsec));
    }
    return NO_ERROR;
}