Plumbing for OPUS encoding.

Webm, OGG container extensions to hold opus.
hooks so we can say that we're recording in opus format.
OMX-specific changes are omitted since our target is Codec2

Bug: 111850384
Test: with separate omx routines
Change-Id: Iecb8b53df3fbd8506d2e6f007602284eb2d0decc
diff --git a/media/libmedia/MediaProfiles.cpp b/media/libmedia/MediaProfiles.cpp
index e0f5a40..08c6a50 100644
--- a/media/libmedia/MediaProfiles.cpp
+++ b/media/libmedia/MediaProfiles.cpp
@@ -48,7 +48,8 @@
     {"amrwb",  AUDIO_ENCODER_AMR_WB},
     {"aac",    AUDIO_ENCODER_AAC},
     {"heaac",  AUDIO_ENCODER_HE_AAC},
-    {"aaceld", AUDIO_ENCODER_AAC_ELD}
+    {"aaceld", AUDIO_ENCODER_AAC_ELD}, 
+    {"opus",   AUDIO_ENCODER_OPUS}
 };
 
 const MediaProfiles::NameToTagMap MediaProfiles::sFileFormatMap[] = {
diff --git a/media/libmedia/include/media/mediarecorder.h b/media/libmedia/include/media/mediarecorder.h
index d8b0fe7..bdf1aae 100644
--- a/media/libmedia/include/media/mediarecorder.h
+++ b/media/libmedia/include/media/mediarecorder.h
@@ -67,7 +67,7 @@
     OUTPUT_FORMAT_AAC_ADTS = 6,
 
     OUTPUT_FORMAT_AUDIO_ONLY_END = 7, // Used in validating the output format.  Should be the
-                                      //  at the end of the audio only output formats.
+                                      // at the end of the audio only output formats.
 
     /* Stream over a socket, limited to a single stream */
     OUTPUT_FORMAT_RTP_AVP = 7,
@@ -81,6 +81,9 @@
     /* HEIC data in a HEIF container */
     OUTPUT_FORMAT_HEIF = 10,
 
+    /* Opus data in a OGG container */
+    OUTPUT_FORMAT_OGG = 11,
+
     OUTPUT_FORMAT_LIST_END // must be last - used to validate format type
 };
 
@@ -92,6 +95,7 @@
     AUDIO_ENCODER_HE_AAC = 4,
     AUDIO_ENCODER_AAC_ELD = 5,
     AUDIO_ENCODER_VORBIS = 6,
+    AUDIO_ENCODER_OPUS = 7,
 
     AUDIO_ENCODER_LIST_END // must be the last - used to validate the audio encoder type
 };
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index e3ae02e..eae52c2 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -46,6 +46,7 @@
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/MediaCodecSource.h>
+#include <media/stagefright/OggWriter.h>
 #include <media/stagefright/PersistentSurface.h>
 #include <media/MediaProfiles.h>
 #include <camera/CameraParameters.h>
@@ -948,6 +949,10 @@
             status = setupMPEG2TSRecording();
             break;
 
+        case OUTPUT_FORMAT_OGG:
+            status = setupOggRecording();
+            break;
+
         default:
             ALOGE("Unsupported output file format: %d", mOutputFormat);
             status = UNKNOWN_ERROR;
@@ -1013,6 +1018,7 @@
         case OUTPUT_FORMAT_AAC_ADTS:
         case OUTPUT_FORMAT_RTP_AVP:
         case OUTPUT_FORMAT_MPEG2TS:
+        case OUTPUT_FORMAT_OGG:
         {
             sp<MetaData> meta = new MetaData;
             int64_t startTimeUs = systemTime() / 1000;
@@ -1113,6 +1119,9 @@
             format->setString("mime", MEDIA_MIMETYPE_AUDIO_AAC);
             format->setInt32("aac-profile", OMX_AUDIO_AACObjectELD);
             break;
+        case AUDIO_ENCODER_OPUS:
+            format->setString("mime", MEDIA_MIMETYPE_AUDIO_OPUS);
+            break;
 
         default:
             ALOGE("Unknown audio encoder: %d", mAudioEncoder);
@@ -1169,6 +1178,13 @@
     return setupRawAudioRecording();
 }
 
+status_t StagefrightRecorder::setupOggRecording() {
+    CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_OGG);
+
+    mWriter = new OggWriter(mOutputFd);
+    return setupRawAudioRecording();
+}
+
 status_t StagefrightRecorder::setupAMRRecording() {
     CHECK(mOutputFormat == OUTPUT_FORMAT_AMR_NB ||
           mOutputFormat == OUTPUT_FORMAT_AMR_WB);
@@ -1813,6 +1829,7 @@
         case AUDIO_ENCODER_AAC:
         case AUDIO_ENCODER_HE_AAC:
         case AUDIO_ENCODER_AAC_ELD:
+        case AUDIO_ENCODER_OPUS:
             break;
 
         default:
@@ -1863,19 +1880,18 @@
         mTotalBitRate += mVideoBitRate;
     }
 
-    if (mOutputFormat != OUTPUT_FORMAT_WEBM) {
-        // Audio source is added at the end if it exists.
-        // This help make sure that the "recoding" sound is suppressed for
-        // camcorder applications in the recorded files.
-        // TODO Audio source is currently unsupported for webm output; vorbis encoder needed.
-        // disable audio for time lapse recording
-        bool disableAudio = mCaptureFpsEnable && mCaptureFps < mFrameRate;
-        if (!disableAudio && mAudioSource != AUDIO_SOURCE_CNT) {
-            err = setupAudioEncoder(writer);
-            if (err != OK) return err;
-            mTotalBitRate += mAudioBitRate;
-        }
+    // Audio source is added at the end if it exists.
+    // This help make sure that the "recoding" sound is suppressed for
+    // camcorder applications in the recorded files.
+    // disable audio for time lapse recording
+    const bool disableAudio = mCaptureFpsEnable && mCaptureFps < mFrameRate;
+    if (!disableAudio && mAudioSource != AUDIO_SOURCE_CNT) {
+        err = setupAudioEncoder(writer);
+        if (err != OK) return err;
+        mTotalBitRate += mAudioBitRate;
+    }
 
+    if (mOutputFormat != OUTPUT_FORMAT_WEBM) {
         if (mCaptureFpsEnable) {
             mp4writer->setCaptureRate(mCaptureFps);
         }
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index faa2e59..2ada301 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -166,6 +166,7 @@
     void setupMPEG4orWEBMMetaData(sp<MetaData> *meta);
     status_t setupAMRRecording();
     status_t setupAACRecording();
+    status_t setupOggRecording();
     status_t setupRawAudioRecording();
     status_t setupRTPRecording();
     status_t setupMPEG2TSRecording();
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 02bb4e0..9aea88a 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -120,6 +120,7 @@
         "MediaMuxer.cpp",
         "NuCachedSource2.cpp",
         "NuMediaExtractor.cpp",
+        "OggWriter.cpp",
         "OMXClient.cpp",
         "OmxInfoBuilder.cpp",
         "RemoteMediaExtractor.cpp",
@@ -159,6 +160,7 @@
         "libstagefright_codecbase",
         "libstagefright_foundation",
         "libstagefright_omx_utils",
+        "libstagefright_opus_common",
         "libstagefright_xmlparser",
         "libRScpp",
         "libhidlallocatorutils",
@@ -179,6 +181,7 @@
         "libstagefright_webm",
         "libstagefright_timedtext",
         "libvpx",
+        "libogg",
         "libwebm",
         "libstagefright_esds",
         "libstagefright_id3",
diff --git a/media/libstagefright/MediaMuxer.cpp b/media/libstagefright/MediaMuxer.cpp
index 98f59b5..9ba2add 100644
--- a/media/libstagefright/MediaMuxer.cpp
+++ b/media/libstagefright/MediaMuxer.cpp
@@ -35,6 +35,7 @@
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/MPEG4Writer.h>
+#include <media/stagefright/OggWriter.h>
 #include <media/stagefright/Utils.h>
 
 namespace android {
@@ -52,6 +53,8 @@
         mWriter = new MPEG4Writer(fd);
     } else if (format == OUTPUT_FORMAT_WEBM) {
         mWriter = new WebmWriter(fd);
+    } else if (format == OUTPUT_FORMAT_OGG) {
+        mWriter = new OggWriter(fd);
     }
 
     if (mWriter != NULL) {
@@ -59,6 +62,8 @@
         if (format == OUTPUT_FORMAT_HEIF) {
             // Note that the key uses recorder file types.
             mFileMeta->setInt32(kKeyFileType, output_format::OUTPUT_FORMAT_HEIF);
+        } else if (format == OUTPUT_FORMAT_OGG) {
+            mFileMeta->setInt32(kKeyFileType, output_format::OUTPUT_FORMAT_OGG);
         }
         mState = INITIALIZED;
     }
diff --git a/media/libstagefright/OggWriter.cpp b/media/libstagefright/OggWriter.cpp
new file mode 100644
index 0000000..ad55c56
--- /dev/null
+++ b/media/libstagefright/OggWriter.cpp
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OggWriter"
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <media/MediaSource.h>
+#include <media/mediarecorder.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OggWriter.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include "OpusHeader.h"
+
+extern "C" {
+#include <ogg/ogg.h>
+}
+
+// store the int32 value in little-endian order.
+static inline void writeint(char *buf, int base, int32_t val) {
+    buf[base + 3] = ((val) >> 24) & 0xff;
+    buf[base + 2] = ((val) >> 16) & 0xff;
+    buf[base + 1] = ((val) >> 8) & 0xff;
+    buf[base] = (val)&0xff;
+}
+
+// linkage between our header OggStreamState and the underlying ogg_stream_state
+// so that consumers of our interface do not require the ogg headers themselves.
+struct OggStreamState : public ogg_stream_state {};
+
+namespace android {
+
+OggWriter::OggWriter(int fd)
+      : mFd(dup(fd)),
+        mInitCheck(mFd < 0 ? NO_INIT : OK) {
+    // empty
+}
+
+OggWriter::~OggWriter() {
+    if (mStarted) {
+        reset();
+    }
+
+    if (mFd != -1) {
+        close(mFd);
+        mFd = -1;
+    }
+
+    free(mOs);
+}
+
+status_t OggWriter::initCheck() const {
+    return mInitCheck;
+}
+
+status_t OggWriter::addSource(const sp<MediaSource>& source) {
+    if (mInitCheck != OK) {
+        return mInitCheck;
+    }
+
+    if (mSource != NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    // Support is limited to single track of Opus audio.
+    const char* mime;
+    source->getFormat()->findCString(kKeyMIMEType, &mime);
+    const char* opus = MEDIA_MIMETYPE_AUDIO_OPUS;
+    if (strncasecmp(mime, opus, strlen(opus))) {
+        ALOGE("Track (%s) other than %s is not supported", mime, opus);
+        return ERROR_UNSUPPORTED;
+    }
+
+    mOs = (OggStreamState*) malloc(sizeof(ogg_stream_state));
+    if (ogg_stream_init((ogg_stream_state*)mOs, rand()) == -1) {
+        ALOGE("ogg stream init failed");
+        return UNKNOWN_ERROR;
+    }
+
+    // Write Ogg headers.
+    int32_t nChannels = 0;
+    if (!source->getFormat()->findInt32(kKeyChannelCount, &nChannels)) {
+        ALOGE("Missing format keys for audio track");
+        source->getFormat()->dumpToLog();
+        return BAD_VALUE;
+    }
+    source->getFormat()->dumpToLog();
+
+    int32_t sampleRate = 0;
+    if (!source->getFormat()->findInt32(kKeySampleRate, &sampleRate)) {
+        ALOGE("Missing format key for sample rate");
+        source->getFormat()->dumpToLog();
+        return UNKNOWN_ERROR;
+    }
+
+    mSampleRate = sampleRate;
+
+    OpusHeader header;
+    header.channels = nChannels;
+    header.num_streams = nChannels;
+    header.num_coupled = 0;
+    header.channel_mapping = ((nChannels > 8) ? 255 : (nChannels > 2));
+    header.gain_db = 0;
+    header.skip_samples = 0;
+
+    // headers are 21-bytes + something driven by channel count
+    // expect numbers in the low 30's here. WriteOpusHeader() will tell us
+    // if things are bad.
+    unsigned char header_data[100];
+    ogg_packet op;
+    ogg_page og;
+
+    const int packet_size = WriteOpusHeader(header, mSampleRate, (uint8_t*)header_data,
+                                            sizeof(header_data));
+
+    if (packet_size < 0) {
+        ALOGE("opus header writing failed");
+        return UNKNOWN_ERROR;
+    }
+    op.packet = header_data;
+    op.bytes = packet_size;
+    op.b_o_s = 1;
+    op.e_o_s = 0;
+    op.granulepos = 0;
+    op.packetno = 0;
+    ogg_stream_packetin((ogg_stream_state*)mOs, &op);
+
+    int ret;
+    while ((ret = ogg_stream_flush((ogg_stream_state*)mOs, &og))) {
+        if (!ret) break;
+        write(mFd, og.header, og.header_len);
+        write(mFd, og.body, og.body_len);
+    }
+
+
+    const char* vendor_string = "libopus";
+    const int vendor_length = strlen(vendor_string);
+    int user_comment_list_length = 0;
+
+    const int comments_length = 8 + 4 + vendor_length + 4 + user_comment_list_length;
+    char* comments = (char*)malloc(comments_length);
+    if (comments == NULL) {
+        ALOGE("failed to allocate ogg comment buffer");
+        return UNKNOWN_ERROR;
+    }
+    memcpy(comments, "OpusTags", 8);
+    writeint(comments, 8, vendor_length);
+    memcpy(comments + 12, vendor_string, vendor_length);
+    writeint(comments, 12 + vendor_length, user_comment_list_length);
+
+    op.packet = (unsigned char*)comments;
+    op.bytes = comments_length;
+    op.b_o_s = 0;
+    op.e_o_s = 0;
+    op.granulepos = 0;
+    op.packetno = 1;
+    ogg_stream_packetin((ogg_stream_state*)mOs, &op);
+
+    while ((ret = ogg_stream_flush((ogg_stream_state*)mOs, &og))) {
+        if (!ret) break;
+        write(mFd, og.header, og.header_len);
+        write(mFd, og.body, og.body_len);
+    }
+
+    mSource = source;
+    free(comments);
+    return OK;
+}
+
+status_t OggWriter::start(MetaData* /* params */) {
+    if (mInitCheck != OK) {
+        return mInitCheck;
+    }
+
+    if (mSource == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    if (mStarted && mPaused) {
+        mPaused = false;
+        mResumed = true;
+        return OK;
+    } else if (mStarted) {
+        // Already started, does nothing
+        return OK;
+    }
+
+    status_t err = mSource->start();
+
+    if (err != OK) {
+        return err;
+    }
+
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+    mReachedEOS = false;
+    mDone = false;
+
+    pthread_create(&mThread, &attr, ThreadWrapper, this);
+    pthread_attr_destroy(&attr);
+
+    mStarted = true;
+
+    return OK;
+}
+
+status_t OggWriter::pause() {
+    if (!mStarted) {
+        return OK;
+    }
+    mPaused = true;
+    return OK;
+}
+
+status_t OggWriter::reset() {
+    if (!mStarted) {
+        return OK;
+    }
+
+    mDone = true;
+
+    void* dummy;
+    pthread_join(mThread, &dummy);
+
+    status_t err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
+    {
+        status_t status = mSource->stop();
+        if (err == OK && (status != OK && status != ERROR_END_OF_STREAM)) {
+            err = status;
+        }
+    }
+
+    mStarted = false;
+    return err;
+}
+
+bool OggWriter::exceedsFileSizeLimit() {
+    if (mMaxFileSizeLimitBytes == 0) {
+        return false;
+    }
+    return mEstimatedSizeBytes > mMaxFileSizeLimitBytes;
+}
+
+bool OggWriter::exceedsFileDurationLimit() {
+    if (mMaxFileDurationLimitUs == 0) {
+        return false;
+    }
+    return mEstimatedDurationUs > mMaxFileDurationLimitUs;
+}
+
+// static
+void* OggWriter::ThreadWrapper(void* me) {
+    return (void*)(uintptr_t) static_cast<OggWriter*>(me)->threadFunc();
+}
+
+status_t OggWriter::threadFunc() {
+    mEstimatedDurationUs = 0;
+    mEstimatedSizeBytes = 0;
+    bool stoppedPrematurely = true;
+    int64_t previousPausedDurationUs = 0;
+    int64_t maxTimestampUs = 0;
+    status_t err = OK;
+
+    prctl(PR_SET_NAME, (unsigned long)"OggWriter", 0, 0, 0);
+
+    while (!mDone) {
+        MediaBufferBase* buffer = nullptr;
+        err = mSource->read(&buffer);
+
+        if (err != OK) {
+            ALOGW("failed to read next buffer");
+            break;
+        }
+
+        if (mPaused) {
+            buffer->release();
+            buffer = nullptr;
+            continue;
+        }
+        mEstimatedSizeBytes += buffer->range_length();
+        if (exceedsFileSizeLimit()) {
+            buffer->release();
+            buffer = nullptr;
+            notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0);
+            ALOGW("estimated size(%" PRId64 ") exceeds limit (%" PRId64 ")",
+                  mEstimatedSizeBytes, mMaxFileSizeLimitBytes);
+            break;
+        }
+        int64_t timestampUs;
+        CHECK(buffer->meta_data().findInt64(kKeyTime, &timestampUs));
+        if (timestampUs > mEstimatedDurationUs) {
+            mEstimatedDurationUs = timestampUs;
+        }
+        if (mResumed) {
+            previousPausedDurationUs += (timestampUs - maxTimestampUs - 20000);
+            mResumed = false;
+        }
+
+        timestampUs -= previousPausedDurationUs;
+
+        ALOGV("time stamp: %" PRId64 ", previous paused duration: %" PRId64, timestampUs,
+              previousPausedDurationUs);
+        if (timestampUs > maxTimestampUs) {
+            maxTimestampUs = timestampUs;
+        }
+
+        if (exceedsFileDurationLimit()) {
+            buffer->release();
+            buffer = nullptr;
+            notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0);
+            ALOGW("estimated duration(%" PRId64 " us) exceeds limit(%" PRId64 " us)",
+                  mEstimatedDurationUs, mMaxFileDurationLimitUs);
+            break;
+        }
+
+        ogg_packet op;
+        ogg_page og;
+        op.packet = (uint8_t*)buffer->data() + buffer->range_offset();
+        op.bytes = (long)buffer->range_length();
+        op.b_o_s = 0;
+        op.e_o_s = mReachedEOS ? 1 : 0;
+        // granulepos is the total number of PCM audio samples @ 48 kHz, up to and
+        // including the current packet.
+        ogg_int64_t granulepos = (48000 * mEstimatedDurationUs) / 1000000;
+        op.granulepos = granulepos;
+
+        // Headers are at packets 0 and 1.
+        op.packetno = 2 + (ogg_int32_t)mCurrentPacketId++;
+        ogg_stream_packetin((ogg_stream_state*)mOs, &op);
+        size_t n = 0;
+
+        while (ogg_stream_flush((ogg_stream_state*)mOs, &og) > 0) {
+            write(mFd, og.header, og.header_len);
+            write(mFd, og.body, og.body_len);
+            n = n + og.header_len + og.body_len;
+        }
+
+        if (n < buffer->range_length()) {
+            buffer->release();
+            buffer = nullptr;
+            err = ERROR_IO;
+            break;
+        }
+
+        if (err != OK) {
+            break;
+        }
+
+        stoppedPrematurely = false;
+
+        buffer->release();
+        buffer = nullptr;
+    }
+
+    // end of stream is an ok thing
+    if (err == ERROR_END_OF_STREAM) {
+        err = OK;
+    }
+
+    if (err == OK && stoppedPrematurely) {
+        err = ERROR_MALFORMED;
+    }
+
+    close(mFd);
+    mFd = -1;
+    mReachedEOS = true;
+
+    return err;
+}
+
+bool OggWriter::reachedEOS() {
+    return mReachedEOS;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/include/media/stagefright/MediaMuxer.h b/media/libstagefright/include/media/stagefright/MediaMuxer.h
index 66f4d72..69d6cde 100644
--- a/media/libstagefright/include/media/stagefright/MediaMuxer.h
+++ b/media/libstagefright/include/media/stagefright/MediaMuxer.h
@@ -49,6 +49,7 @@
         OUTPUT_FORMAT_WEBM        = 1,
         OUTPUT_FORMAT_THREE_GPP   = 2,
         OUTPUT_FORMAT_HEIF        = 3,
+        OUTPUT_FORMAT_OGG         = 4,
         OUTPUT_FORMAT_LIST_END // must be last - used to validate format type
     };
 
diff --git a/media/libstagefright/include/media/stagefright/OggWriter.h b/media/libstagefright/include/media/stagefright/OggWriter.h
new file mode 100644
index 0000000..e3837cd
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/OggWriter.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OGG_WRITER_H_
+
+#define OGG_WRITER_H_
+
+#include <stdio.h>
+
+#include <media/stagefright/MediaWriter.h>
+#include <utils/threads.h>
+
+struct OggStreamState;
+
+namespace android {
+
+struct OggWriter : public MediaWriter {
+    OggWriter(int fd);
+
+    status_t initCheck() const;
+
+    virtual status_t addSource(const sp<MediaSource>& source);
+    virtual bool reachedEOS();
+    virtual status_t start(MetaData* params = NULL);
+    virtual status_t stop() { return reset(); }
+    virtual status_t pause();
+
+protected:
+    ~OggWriter();
+
+private:
+    int mFd;
+    status_t mInitCheck;
+    sp<MediaSource> mSource;
+    bool mStarted = false;
+    volatile bool mPaused = false;
+    volatile bool mResumed = false;
+    volatile bool mDone;
+    volatile bool mReachedEOS;
+    pthread_t mThread;
+    int64_t mSampleRate;
+    int64_t mEstimatedSizeBytes;
+    int64_t mEstimatedDurationUs;
+
+    static void* ThreadWrapper(void*);
+    status_t threadFunc();
+    bool exceedsFileSizeLimit();
+    bool exceedsFileDurationLimit();
+    status_t reset();
+
+    int32_t mCurrentPacketId;
+    OggStreamState* mOs = nullptr;
+
+    OggWriter(const OggWriter&);
+    OggWriter& operator=(const OggWriter&);
+};
+
+}  // namespace android
+
+#endif  // OGG_WRITER_H_
diff --git a/media/libstagefright/opus/Android.bp b/media/libstagefright/opus/Android.bp
new file mode 100644
index 0000000..c5086ec
--- /dev/null
+++ b/media/libstagefright/opus/Android.bp
@@ -0,0 +1,21 @@
+cc_library_shared {
+    name: "libstagefright_opus_common",
+    vendor_available: true,
+
+    export_include_dirs: ["include"],
+
+    srcs: ["OpusHeader.cpp"],
+
+    shared_libs: ["liblog"],
+
+    cflags: ["-Werror"],
+
+    sanitize: {
+        integer_overflow: true,
+        cfi: true,
+        diag: {
+            integer_overflow: true,
+            cfi: true,
+        },
+    },
+}
\ No newline at end of file
diff --git a/media/libstagefright/opus/OpusHeader.cpp b/media/libstagefright/opus/OpusHeader.cpp
new file mode 100644
index 0000000..e4a460c
--- /dev/null
+++ b/media/libstagefright/opus/OpusHeader.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoftOpus"
+
+#include <cstring>
+#include <stdint.h>
+
+#include <log/log.h>
+
+#include "OpusHeader.h"
+
+namespace android {
+
+// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
+// mappings for up to 8 channels. This information is part of the Vorbis I
+// Specification:
+// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
+constexpr int kMaxChannels = 8;
+
+constexpr uint8_t kOpusChannelMap[kMaxChannels][kMaxChannels] = {
+        {0},
+        {0, 1},
+        {0, 2, 1},
+        {0, 1, 2, 3},
+        {0, 4, 1, 2, 3},
+        {0, 4, 1, 2, 3, 5},
+        {0, 4, 1, 2, 3, 5, 6},
+        {0, 6, 1, 2, 3, 4, 5, 7},
+};
+
+// Opus always has a 48kHz output rate. This is true for all Opus, not just this
+// implementation.
+constexpr int kRate = 48000;
+// Size of the Opus header excluding optional mapping information.
+constexpr size_t kOpusHeaderSize = 19;
+// Offset to magic string that starts Opus header.
+constexpr size_t kOpusHeaderLabelOffset = 0;
+// Offset to Opus version in the Opus header.
+constexpr size_t kOpusHeaderVersionOffset = 8;
+// Offset to the channel count byte in the Opus header.
+constexpr size_t kOpusHeaderChannelsOffset = 9;
+// Offset to the pre-skip value in the Opus header.
+constexpr size_t kOpusHeaderSkipSamplesOffset = 10;
+// Offset to sample rate in the Opus header.
+constexpr size_t kOpusHeaderSampleRateOffset = 12;
+// Offset to the gain value in the Opus header.
+constexpr size_t kOpusHeaderGainOffset = 16;
+// Offset to the channel mapping byte in the Opus header.
+constexpr size_t kOpusHeaderChannelMappingOffset = 18;
+// Opus Header contains a stream map. The mapping values are in the header
+// beyond the always present |kOpusHeaderSize| bytes of data. The mapping
+// data contains stream count, coupling information, and per channel mapping
+// values:
+//   - Byte 0: Number of streams.
+//   - Byte 1: Number coupled.
+//   - Byte 2: Starting at byte 2 are |header->channels| uint8 mapping
+//             values.
+// Offset to the number of streams in the Opus header.
+constexpr size_t kOpusHeaderNumStreamsOffset = 19;
+// Offset to the number of streams that are coupled in the Opus header.
+constexpr size_t kOpusHeaderNumCoupledStreamsOffset = 20;
+// Offset to the stream to channel mapping in the Opus header.
+constexpr size_t kOpusHeaderStreamMapOffset = 21;
+// Maximum packet size used in Xiph's opusdec.
+constexpr int kMaxOpusOutputPacketSizeSamples = 960 * 6;
+
+// Default audio output channel layout. Used to initialize |stream_map| in
+// OpusHeader, and passed to opus_multistream_decoder_create() when the header
+// does not contain mapping information. The values are valid only for mono and
+// stereo output: Opus streams with more than 2 channels require a stream map.
+constexpr int kMaxChannelsWithDefaultLayout = 2;
+constexpr uint8_t kDefaultOpusChannelLayout[kMaxChannelsWithDefaultLayout] = {0, 1};
+
+static uint16_t ReadLE16(const uint8_t* data, size_t data_size, uint32_t read_offset) {
+    // check whether the 2nd byte is within the buffer
+    if (read_offset + 1 >= data_size) return 0;
+    uint16_t val;
+    val = data[read_offset];
+    val |= data[read_offset + 1] << 8;
+    return val;
+}
+
+// Parses Opus Header. Header spec: http://wiki.xiph.org/OggOpus#ID_Header
+bool ParseOpusHeader(const uint8_t* data, size_t data_size, OpusHeader* header) {
+    if (data_size < kOpusHeaderSize) {
+        ALOGV("Header size is too small.");
+        return false;
+    }
+    header->channels = data[kOpusHeaderChannelsOffset];
+
+    if (header->channels < 1 || header->channels > kMaxChannels) {
+        ALOGV("Invalid Header, bad channel count: %d", header->channels);
+        return false;
+    }
+    header->skip_samples = ReadLE16(data, data_size, kOpusHeaderSkipSamplesOffset);
+    header->gain_db = static_cast<int16_t>(ReadLE16(data, data_size, kOpusHeaderGainOffset));
+    header->channel_mapping = data[kOpusHeaderChannelMappingOffset];
+    if (!header->channel_mapping) {
+        if (header->channels > kMaxChannelsWithDefaultLayout) {
+            ALOGV("Invalid Header, missing stream map.");
+            return false;
+        }
+        header->num_streams = 1;
+        header->num_coupled = header->channels > 1;
+        header->stream_map[0] = 0;
+        header->stream_map[1] = 1;
+        return true;
+    }
+    if (data_size < kOpusHeaderStreamMapOffset + header->channels) {
+        ALOGV("Invalid stream map; insufficient data for current channel "
+              "count: %d",
+              header->channels);
+        return false;
+    }
+    header->num_streams = data[kOpusHeaderNumStreamsOffset];
+    header->num_coupled = data[kOpusHeaderNumCoupledStreamsOffset];
+    if (header->num_streams + header->num_coupled != header->channels) {
+        ALOGV("Inconsistent channel mapping.");
+        return false;
+    }
+    for (int i = 0; i < header->channels; ++i)
+        header->stream_map[i] = data[kOpusHeaderStreamMapOffset + i];
+    return true;
+}
+
+int WriteOpusHeader(const OpusHeader &header, int input_sample_rate,
+                    uint8_t* output, size_t output_size) {
+    // See https://wiki.xiph.org/OggOpus#ID_Header.
+    const size_t total_size = kOpusHeaderStreamMapOffset + header.channels;
+    if (output_size < total_size) {
+        ALOGE("Output buffer too small for header.");
+        return -1;
+    }
+
+    // ensure entire header is cleared, even though we overwrite much of it below
+    memset(output, 0, output_size);
+
+    // Set magic signature.
+    memcpy(output + kOpusHeaderLabelOffset, "OpusHead", 8);
+    // Set Opus version.
+    output[kOpusHeaderVersionOffset] = 1;
+    // Set channel count.
+    output[kOpusHeaderChannelsOffset] = (uint8_t)header.channels;
+    // Set pre-skip
+    memcpy(output + kOpusHeaderSkipSamplesOffset, &header.skip_samples, sizeof(uint16_t));
+    // Set original input sample rate in Hz.
+    memcpy(output + kOpusHeaderSampleRateOffset, &input_sample_rate, sizeof(uint32_t));
+    // Set output gain in dB.
+    memcpy(output + kOpusHeaderGainOffset, &header.gain_db, sizeof(uint16_t));
+
+    if (header.channels > 2) {
+        // Set channel mapping
+        output[kOpusHeaderChannelMappingOffset] = 1;
+        // Assuming no coupled streams. This should actually be
+        // channels() - |coupled_streams|.
+        output[kOpusHeaderNumStreamsOffset] = header.channels;
+        output[kOpusHeaderNumCoupledStreamsOffset] = 0;
+
+        // Set the actual stream map.
+        for (int i = 0; i < header.channels; ++i) {
+            output[kOpusHeaderStreamMapOffset + i] = kOpusChannelMap[header.channels - 1][i];
+        }
+        return kOpusHeaderStreamMapOffset + header.channels + 1;
+    } else {
+        output[kOpusHeaderChannelMappingOffset] = 0;
+        return kOpusHeaderChannelMappingOffset + 1;
+    }
+}
+
+}  // namespace android
diff --git a/media/libstagefright/opus/include/OpusHeader.h b/media/libstagefright/opus/include/OpusHeader.h
new file mode 100644
index 0000000..f9f79cd
--- /dev/null
+++ b/media/libstagefright/opus/include/OpusHeader.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The Opus specification is part of IETF RFC 6716:
+ * http://tools.ietf.org/html/rfc6716
+ */
+
+#ifndef OPUS_HEADER_H_
+#define OPUS_HEADER_H_
+
+namespace android {
+
+struct OpusHeader {
+    int channels;
+    int channel_mapping;
+    int num_streams;
+    int num_coupled;
+    int16_t gain_db;
+    int skip_samples;
+    uint8_t stream_map[8];
+};
+
+bool ParseOpusHeader(const uint8_t* data, size_t data_size, OpusHeader* header);
+int WriteOpusHeader(const OpusHeader &header, int input_sample_rate, uint8_t* output, size_t output_size);
+}  // namespace android
+
+#endif  // OPUS_HEADER_H_
diff --git a/media/libstagefright/webm/Android.bp b/media/libstagefright/webm/Android.bp
index 64ecc2d..1f840b7 100644
--- a/media/libstagefright/webm/Android.bp
+++ b/media/libstagefright/webm/Android.bp
@@ -28,6 +28,7 @@
 
     shared_libs: [
         "libstagefright_foundation",
+        "libstagefright_opus_common",
         "libutils",
         "liblog",
     ],
diff --git a/media/libstagefright/webm/WebmElement.cpp b/media/libstagefright/webm/WebmElement.cpp
index a5120b9..4d504e0 100644
--- a/media/libstagefright/webm/WebmElement.cpp
+++ b/media/libstagefright/webm/WebmElement.cpp
@@ -305,6 +305,7 @@
 }
 
 sp<WebmElement> WebmElement::AudioTrackEntry(
+        const char *codec,
         int chans,
         double rate,
         const sp<ABuffer> &buf,
@@ -322,7 +323,7 @@
             uid,
             lacing,
             lang,
-            "A_VORBIS",
+            codec,
             kAudioType,
             trackEntryFields);
 
diff --git a/media/libstagefright/webm/WebmElement.h b/media/libstagefright/webm/WebmElement.h
index ffbba1b..a94c23f 100644
--- a/media/libstagefright/webm/WebmElement.h
+++ b/media/libstagefright/webm/WebmElement.h
@@ -50,6 +50,7 @@
     static sp<WebmElement> SegmentInfo(uint64_t scale = 1000000, double dur = 0);
 
     static sp<WebmElement> AudioTrackEntry(
+            const char *codec,
             int chans,
             double rate,
             const sp<ABuffer> &buf,
diff --git a/media/libstagefright/webm/WebmWriter.cpp b/media/libstagefright/webm/WebmWriter.cpp
index 4d73eb8..7b4b23a 100644
--- a/media/libstagefright/webm/WebmWriter.cpp
+++ b/media/libstagefright/webm/WebmWriter.cpp
@@ -23,6 +23,8 @@
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <OpusHeader.h>
 
 #include <utils/Errors.h>
 
@@ -112,46 +114,102 @@
 // static
 sp<WebmElement> WebmWriter::audioTrack(const sp<MetaData>& md) {
     int32_t nChannels, samplerate;
-    uint32_t type;
-    const void *headerData1;
-    const char headerData2[] = { 3, 'v', 'o', 'r', 'b', 'i', 's', 7, 0, 0, 0,
-            'a', 'n', 'd', 'r', 'o', 'i', 'd', 0, 0, 0, 0, 1 };
-    const void *headerData3;
-    size_t headerSize1, headerSize2 = sizeof(headerData2), headerSize3;
+    const char* mimeType;
 
     if (!md->findInt32(kKeyChannelCount, &nChannels)
-            || !md->findInt32(kKeySampleRate, &samplerate)
-            || !md->findData(kKeyVorbisInfo, &type, &headerData1, &headerSize1)
-            || !md->findData(kKeyVorbisBooks, &type, &headerData3, &headerSize3)) {
+        || !md->findInt32(kKeySampleRate, &samplerate)
+        || !md->findCString(kKeyMIMEType, &mimeType)) {
         ALOGE("Missing format keys for audio track");
         md->dumpToLog();
         return NULL;
     }
 
-    size_t codecPrivateSize = 1;
-    codecPrivateSize += XiphLaceCodeLen(headerSize1);
-    codecPrivateSize += XiphLaceCodeLen(headerSize2);
-    codecPrivateSize += headerSize1 + headerSize2 + headerSize3;
+    if (!strncasecmp(mimeType, MEDIA_MIMETYPE_AUDIO_OPUS, strlen(MEDIA_MIMETYPE_AUDIO_OPUS))) {
+        // Opus in WebM is a well-known, yet under-documented, format. The codec private data
+        // of the track is an Opus Ogg header (https://tools.ietf.org/html/rfc7845#section-5.1)
+        // The name of the track isn't standardized, its value should be "A_OPUS".
+        OpusHeader header;
+        header.channels = nChannels;
+        header.num_streams = nChannels;
+        header.num_coupled = 0;
+        // - Channel mapping family (8 bits unsigned)
+        //  --  0 = one stream: mono or L,R stereo
+        //  --  1 = channels in vorbis spec order: mono or L,R stereo or ... or FL,C,FR,RL,RR,LFE, ...
+        //  --  2..254 = reserved (treat as 255)
+        //  --  255 = no defined channel meaning
+        //
+        //  our implementation encodes:  0, 1, or 255
+        header.channel_mapping = ((nChannels > 8) ? 255 : (nChannels > 2));
+        header.gain_db = 0;
+        header.skip_samples = 0;
 
-    off_t off = 0;
-    sp<ABuffer> codecPrivateBuf = new ABuffer(codecPrivateSize);
-    uint8_t *codecPrivateData = codecPrivateBuf->data();
-    codecPrivateData[off++] = 2;
+        // headers are 21-bytes + something driven by channel count
+        // expect numbers in the low 30's here. WriteOpusHeader() will tell us
+        // if things are bad.
+        unsigned char header_data[100];
+        int headerSize = WriteOpusHeader(header, samplerate, (uint8_t*)header_data,
+                                            sizeof(header_data));
 
-    off += XiphLaceEnc(codecPrivateData + off, headerSize1);
-    off += XiphLaceEnc(codecPrivateData + off, headerSize2);
+        if (headerSize < 0) {
+            // didn't fill out that header for some reason
+            ALOGE("failed to generate OPUS header");
+            return NULL;
+        }
 
-    memcpy(codecPrivateData + off, headerData1, headerSize1);
-    off += headerSize1;
-    memcpy(codecPrivateData + off, headerData2, headerSize2);
-    off += headerSize2;
-    memcpy(codecPrivateData + off, headerData3, headerSize3);
+        size_t codecPrivateSize = 0;
+        codecPrivateSize += headerSize;
 
-    sp<WebmElement> entry = WebmElement::AudioTrackEntry(
-            nChannels,
-            samplerate,
-            codecPrivateBuf);
-    return entry;
+        off_t off = 0;
+        sp<ABuffer> codecPrivateBuf = new ABuffer(codecPrivateSize);
+        uint8_t* codecPrivateData = codecPrivateBuf->data();
+
+        memcpy(codecPrivateData + off, (uint8_t*)header_data, headerSize);
+        sp<WebmElement> entry =
+                WebmElement::AudioTrackEntry("A_OPUS", nChannels, samplerate, codecPrivateBuf);
+        return entry;
+    } else if (!strncasecmp(mimeType,
+                            MEDIA_MIMETYPE_AUDIO_VORBIS,
+                            strlen(MEDIA_MIMETYPE_AUDIO_VORBIS))) {
+        uint32_t type;
+        const void *headerData1;
+        const char headerData2[] = { 3, 'v', 'o', 'r', 'b', 'i', 's', 7, 0, 0, 0,
+                'a', 'n', 'd', 'r', 'o', 'i', 'd', 0, 0, 0, 0, 1 };
+        const void *headerData3;
+        size_t headerSize1, headerSize2 = sizeof(headerData2), headerSize3;
+
+        if (!md->findData(kKeyVorbisInfo, &type, &headerData1, &headerSize1)
+            || !md->findData(kKeyVorbisBooks, &type, &headerData3, &headerSize3)) {
+            ALOGE("Missing header format keys for vorbis track");
+            md->dumpToLog();
+            return NULL;
+        }
+
+        size_t codecPrivateSize = 1;
+        codecPrivateSize += XiphLaceCodeLen(headerSize1);
+        codecPrivateSize += XiphLaceCodeLen(headerSize2);
+        codecPrivateSize += headerSize1 + headerSize2 + headerSize3;
+
+        off_t off = 0;
+        sp<ABuffer> codecPrivateBuf = new ABuffer(codecPrivateSize);
+        uint8_t *codecPrivateData = codecPrivateBuf->data();
+        codecPrivateData[off++] = 2;
+
+        off += XiphLaceEnc(codecPrivateData + off, headerSize1);
+        off += XiphLaceEnc(codecPrivateData + off, headerSize2);
+
+        memcpy(codecPrivateData + off, headerData1, headerSize1);
+        off += headerSize1;
+        memcpy(codecPrivateData + off, headerData2, headerSize2);
+        off += headerSize2;
+        memcpy(codecPrivateData + off, headerData3, headerSize3);
+
+        sp<WebmElement> entry =
+                WebmElement::AudioTrackEntry("A_VORBIS", nChannels, samplerate, codecPrivateBuf);
+        return entry;
+    } else {
+        ALOGE("Track (%s) is not a supported audio format", mimeType);
+        return NULL;
+    }
 }
 
 size_t WebmWriter::numTracks() {
@@ -382,16 +440,18 @@
     const char *vp8 = MEDIA_MIMETYPE_VIDEO_VP8;
     const char *vp9 = MEDIA_MIMETYPE_VIDEO_VP9;
     const char *vorbis = MEDIA_MIMETYPE_AUDIO_VORBIS;
+    const char* opus = MEDIA_MIMETYPE_AUDIO_OPUS;
 
     size_t streamIndex;
     if (!strncasecmp(mime, vp8, strlen(vp8)) ||
         !strncasecmp(mime, vp9, strlen(vp9))) {
         streamIndex = kVideoIndex;
-    } else if (!strncasecmp(mime, vorbis, strlen(vorbis))) {
+    } else if (!strncasecmp(mime, vorbis, strlen(vorbis)) ||
+               !strncasecmp(mime, opus, strlen(opus))) {
         streamIndex = kAudioIndex;
     } else {
-        ALOGE("Track (%s) other than %s, %s or %s is not supported",
-              mime, vp8, vp9, vorbis);
+        ALOGE("Track (%s) other than %s, %s, %s, or %s is not supported",
+              mime, vp8, vp9, vorbis, opus);
         return ERROR_UNSUPPORTED;
     }