Android 13 - Marco multimedia (9) - NuPlayer::Decoder

En esta sección aprenderemos sobre NuPlayer::Decoder y cómo convertir MediaCodec en un potente Decoder. Esta sección hablará de antemano sobre el contenido relacionado con MediaCodec. Si no lo comprende claramente, puede omitir este artículo primero. Al principio pensé que la parte del Decodificador era simple, pero cuanto más leía, más me daba cuenta de mi ignorancia. ¡El código fuente de Android es realmente un gran tesoro!
ps: las letras mayúsculas en este artículo Decoderse refieren a NuPlayer::Decoder, y las letras minúsculas decoderse refieren a mediacodec y al decodificador real subyacente.

1、Base del decodificador

Primero mire la clase base de NuPlayer::Decoder DecoderBase:

struct NuPlayer::DecoderBase : public AHandler {
    
    
    explicit DecoderBase(const sp<AMessage> &notify);
    void configure(const sp<AMessage> &format);
    void init();
    void setParameters(const sp<AMessage> &params);
    // Synchronous call to ensure decoder will not request or send out data.
    void pause();
    void setRenderer(const sp<Renderer> &renderer);
    virtual status_t setVideoSurface(const sp<Surface> &) {
    
     return INVALID_OPERATION; }
    void signalFlush();
    void signalResume(bool notifyComplete);
    void initiateShutdown();
    virtual sp<AMessage> getStats() {
    
    
        return mStats;
    }
protected:
	virtual void onMessageReceived(const sp<AMessage> &msg);

    virtual void onConfigure(const sp<AMessage> &format) = 0;
    virtual void onSetParameters(const sp<AMessage> &params) = 0;
    virtual void onSetRenderer(const sp<Renderer> &renderer) = 0;
    virtual void onResume(bool notifyComplete) = 0;
    virtual void onFlush() = 0;
    virtual void onShutdown(bool notifyComplete) = 0;
    void onRequestInputBuffers();
    virtual bool doRequestBuffers() = 0;
}

DecoderBase define todas las interfaces que NuPlayer puede llamar Decoder. Puede ver que la cantidad de interfaces es bastante rara. No hay métodos de inicio, parada, reinicio, búsqueda, etc.. En este momento, algunas personas pueden tener preguntas. ¿Por qué? ¿Estas interfaces son llamadas por mi capa superior de nivel inferior?, ¿pero ya no están? De hecho, ya hemos explicado algunas de las interfaces en artículos anteriores, por lo que no entraremos en detalles aquí.

Entendamos para qué se utilizan estas interfaces y cómo usarlas:

  • 构造函数: Pasa un objeto AMessage para lanzar eventos al estado;
  • configure: Pase la información de formato analizada por la Fuente durante el proceso de preparación. La información de formato incluye tipo mime, superficie, seguridad, ancho, alto, criptografía, csd, etc.; cree una instancia de MediaCodec, configúrela e iníciela;
  • init: Regístrese en ALooper;
  • setParameters: establece los parámetros pasados ​​desde la capa superior al decodificador;
  • pause: Este método en realidad no es útil, aprenderemos más sobre por qué es inútil más adelante;
  • setRenderer: Establecer renderizado. Los datos decodificados por el decodificador se enviarán al renderizado para avsync. Si son datos de audio, se escribirán directamente en AudioTrack;
  • setVideoSurface: Restablece la superficie, el decodificador de audio no necesita este método;
  • signalFlush:flush, actualiza el búfer de entrada/salida del decodificador;
  • signalResume:Restaurar el proceso de decodificación del decodificador;
  • initiateShutdown: Detenga el proceso de decodificación y libere los recursos relacionados;
  • getStats: Obtenga el estado del decodificador actual, como información de formato, la cantidad de fotogramas resueltos actualmente, la cantidad de fotogramas descartados, etc.
  • 其他:onConfigure y otros métodos serán implementados por un decodificador específico. Si el audio no usa descarga, el decodificador de audio/video usará el mismo proceso.

2. Creación e inicio del decodificador.

Del código fuente de NuPlayer, podemos saber que después de llamar al método de inicio, se creará un Decoder. Aquí, se Decoderhereda de DecoderBase. Luego, se llaman Decoder.init y Decoder.configure. Aquí, se inicia el Decoder:

status_t NuPlayer::instantiateDecoder(
        bool audio, sp<DecoderBase> *decoder, bool checkAudioModeChange) {
    
    
	sp<AMessage> format = mSource->getFormat(audio);
    *decoder = new Decoder(
          notify, mSource, mPID, mUID, mRenderer, mSurface, mCCDecoder);
    (*decoder)->init();
    (*decoder)->configure(format);
}

El método init es muy simple: simplemente registra el AHandler en el ALooper. Quiero plantear dos preguntas aquí: this¿A quién se refiere RegisterHandler?

void NuPlayer::DecoderBase::init() {
    
    
    mDecoderLooper->registerHandler(this);
}

¿El mensaje en onRequestInputBuffers será procesado primero por Decoder::onMessageReceived o DecoderBase::onMessageReceived? Si no está seguro de cuál es la respuesta, puede buscar polimorfismo.

void NuPlayer::DecoderBase::onRequestInputBuffers() {
    
    
	....
    sp<AMessage> msg = new AMessage(kWhatRequestInputBuffers, this);
    msg->post(10 * 1000LL);
}

Si continúa mirando hacia abajo, eventualmente se llamará a configure en el método onConfigure:

void NuPlayer::Decoder::onConfigure(const sp<AMessage> &format) {
    
    
    ++mBufferGeneration;

    AString mime;
    CHECK(format->findString("mime", &mime));

    mIsAudio = !strncasecmp("audio/", mime.c_str(), 6);
    mComponentName = mime;
    mComponentName.append(" decoder");

    mCodec = MediaCodec::CreateByType(
            mCodecLooper, mime.c_str(), false /* encoder */, NULL /* err */, mPid, mUid, format);
    int32_t secure = 0;
    if (format->findInt32("secure", &secure) && secure != 0) {
    
    
        if (mCodec != NULL) {
    
    
            mCodec->getName(&mComponentName);
            mComponentName.append(".secure");
            mCodec->release();
            mCodec = MediaCodec::CreateByComponentName(
                    mCodecLooper, mComponentName.c_str(), NULL /* err */, mPid, mUid);
        }
    }
    err = mCodec->configure(
            format, mSurface, crypto, 0 /* flags */);
   	rememberCodecSpecificData(format);
    sp<AMessage> reply = new AMessage(kWhatCodecNotify, this);
    mCodec->setCallback(reply);

    err = mCodec->start();
}
  1. Para encontrar formato mime, se requiere esto;
  2. Llame a CreateByType para crear una instancia de MediaCodec. Si el formato tiene un campo seguro, llame a CreateByComponentName para crear un componente seguro;
  3. Al llamar a configure para configurar MediaCodec, debe pasar el formato del flujo de código. Veremos qué se requiere en el formato más adelante. La superficie pasada aquí no es NULL porque hay un juicio en NuPlayer. Si la superficie es NULL, no se creará el códec para el vídeo;
  4. Almacene los datos específicos del códec (búfer csd) en el formato. Estos búferes registran información de flujo de código, como sps pps y otra información en flujos de código h264 y h265. Para algunos decodificadores, esta información se debe pasar, mientras que algunos decodificadores pueden hacerlo. por sí mismos. La información analizada en el flujo de código depende de la implementación del decodificador de cada empresa;
  5. Registre la devolución de llamada para MediaCodec y déjelo 异步funcionar de la siguiente manera;
  6. Llame al método de inicio para iniciar el decodificador e iniciar todo el proceso de lectura, decodificación y representación de datos.

El siguiente contenido es mi humilde opinión y es un poco extenso, si no te gusta, omítelo.

Además de estos, también debemos mirar un miembro.¿Qué mBufferGenerationhace esto? De hecho, hemos dicho antes que Media usa la idea de múltiples generaciones, o trucos, entonces, ¿para qué se usa la generación aquí?

Buscamos mBufferGeneration en el código y descubrimos que su valor se modificará en onConfigure, y . Estos cuatro métodos tienen una cosa en común: operarán MediaCodec y cambiarán el estado doFlushde MediaCodec, lo que afectará el estado del búfer MediaCodec.onShutdownhandleError

Veamos dónde se utilizará mBufferGeneration:

bool NuPlayer::Decoder::isStaleReply(const sp<AMessage> &msg) {
    
    
    int32_t generation;
    CHECK(msg->findInt32("generation", &generation));
    return generation != mBufferGeneration;
}

void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) {
    
    
	switch (msg->what()) {
    
    
        case kWhatRenderBuffer:
        {
    
    
            if (!isStaleReply(msg)) {
    
    
                onRenderBuffer(msg);
            }
            break;
        }
    }
}

Cuando el búfer de salida de video se envía al Renderer para avsync y luego se devuelve para renderizar, determinará si la mBufferGeneration actual ha cambiado. ¿Cuál es el propósito de hacer esto aquí?

Tengo entendido que después de que el búfer de salida se envía al renderizador para su procesamiento, el renderizador llamará a métodos relacionados con el renderizado, pero en este momento el estado del búfer puede haber cambiado, como vaciado o apagado, y el búfer ya no debe ser Procesamiento, use mBufferGeneration para juzgar y podrá omitir el paso de procesamiento.

entregar un asuntoOtros componentesAl procesar, registre la generación actual, cuando finaliza el procesamiento del evento y =Volver al componente actualCuándo, se decide en función de la generación actual si es necesario descartar el contenido. Android utiliza técnicas de generación en ACodec, NuPlayer::Source, Renderer y otras implementaciones para manejar transacciones durante las transiciones de estado.

Lo anterior es mi comprensión de la generación cuando leí el código por primera vez. Después de leerlo nuevamente, obtuve algunos conocimientos nuevos:

Los mecanismos de mensajería asincrónica ALooper y AHandler se utilizan ampliamente en NuPlayer. La asincronía aquí es relativa a la persona que llama. Por ejemplo, cuando NuPlayer llama al método de configuración de Decoder, NuPlayer finaliza después de llamarlo. En este momento, el objeto MediaCodec en Decoder puede Aún no se ha creado. , esto es asíncrono. Pero para el Decoder, todas las llamadas (mensajes enviados) son procesadas una por una por los hilos en el Looper, por lo que el interior del Decoder esSincronizaciónde.

¿Por qué decís esto? Echemos un vistazo a quién enviará mensajes al Decoder: NuPlayer, Renderer y MediaCodec. Es posible que todos envíen mensajes al Decoder al mismo tiempo o uno tras otro. ¿Qué problemas causará esto? Tome el renderizador como ejemplo:

    sp<AMessage> reply = new AMessage(kWhatRenderBuffer, this);
    reply->setSize("buffer-ix", index);
    reply->setInt32("generation", mBufferGeneration);
    reply->setSize("size", size);

Después de que el Decodificador reciba el evento CB_OUTPUT_AVAILABLE enviado por MediaCodec, guardará mBufferGeneration en msg y lo pasará al Renderer. Después de que el Renderer complete la sincronización, reenviará el mensaje al Decoder. Sin embargo, si las llamadas de la capa superior se restablecen durante el proceso de sincronización y el Decoder también procesa el evento, entonces el Decoder ya no podrá procesar los mensajes de renderizado enviados por el Renderer (todo el proceso se detuvo y el componente se liberó). .

Por favor agregue la descripción de la imagen.

La generación desempeña el papel de registro de estado: cuando el estado cambia, los mensajes que dependen del estado ya no se procesarán. Es similar a algunos estados específicos, como el uso del estado en MediaPlayer.cpp, pero el uso de la generación es más simple: no presta atención al estado específico, solo se centra en si se llama al método que cambia el estado del decodificador. .

3、Iniciar

Citamos la descripción de inicio en la sección anterior: 开启整个数据读取、数据解码 以及 数据渲染流程Inicio no solo inicia MediaCodec, sino que también impulsa el funcionamiento de todos los componentes, echemos un vistazo.

Veamos primero la devolución de llamada clave de MediaCodec:

void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) {
    
    
    switch (msg->what()) {
    
    
        case kWhatCodecNotify:
        {
    
    
            int32_t cbID;
            CHECK(msg->findInt32("callbackID", &cbID));
            switch (cbID) {
    
    
                case MediaCodec::CB_INPUT_AVAILABLE:
                {
    
    
                    int32_t index;
                    CHECK(msg->findInt32("index", &index));

                    handleAnInputBuffer(index);
                    break;
                }

                case MediaCodec::CB_OUTPUT_AVAILABLE:
                {
    
    
                    int32_t index;
                    size_t offset;
                    size_t size;
                    int64_t timeUs;
                    int32_t flags;

                    CHECK(msg->findInt32("index", &index));
                    CHECK(msg->findSize("offset", &offset));
                    CHECK(msg->findSize("size", &size));
                    CHECK(msg->findInt64("timeUs", &timeUs));
                    CHECK(msg->findInt32("flags", &flags));

                    handleAnOutputBuffer(index, offset, size, timeUs, flags);
                    break;
                }
            }
        }
    }
}

MediaCodec kWhatCodecNotifyprocesa el mensaje enviándolo al decodificador y usa callbackID para distinguir el contenido enviado. Los cuatro más utilizados son CB_INPUT_AVAILABLE,,, y aquí solo miramos la entrada y la salida CB_OUTPUT_AVAILABLE.CB_ERRORCB_OUTPUT_FORMAT_CHANGED

3.1、CB_INPUT_AVAILABLE

Después de recibir el evento de entrada lanzado por MediaCodec, se llamará al método handleAnInputBuffer y el parámetro de entrada es la identificación del búfer de entrada.

bool NuPlayer::Decoder::handleAnInputBuffer(size_t index) {
    
    
	// 判断是否处在 处理不连续码流 的状态
    if (isDiscontinuityPending()) {
    
    
        return false;
    }

    sp<MediaCodecBuffer> buffer;
    mCodec->getInputBuffer(index, &buffer);

    if (index >= mInputBuffers.size()) {
    
    
        for (size_t i = mInputBuffers.size(); i <= index; ++i) {
    
    
            mInputBuffers.add();
            mInputBufferIsDequeued.add();
            mMediaBuffers.editItemAt(i) = NULL;
            mInputBufferIsDequeued.editItemAt(i) = false;
        }
    }
    mInputBuffers.editItemAt(index) = buffer;
    mInputBufferIsDequeued.editItemAt(index) = true;

	// 如果有码流不连续的情况,恢复播放后重新发送csd buffer
    if (!mCSDsToSubmit.isEmpty()) {
    
    
        sp<AMessage> msg = new AMessage();
        msg->setSize("buffer-ix", index);

        sp<ABuffer> buffer = mCSDsToSubmit.itemAt(0);
        msg->setBuffer("buffer", buffer);
        mCSDsToSubmit.removeAt(0);
        if (!onInputBufferFetched(msg)) {
    
    
            handleError(UNKNOWN_ERROR);
            return false;
        }
        return true;
    }
	// 如果有 buffer 没有成功写入 mediacodec 的情况,尝试重新写入
    while (!mPendingInputMessages.empty()) {
    
    
        sp<AMessage> msg = *mPendingInputMessages.begin();
        if (!onInputBufferFetched(msg)) {
    
    
            break;
        }
        mPendingInputMessages.erase(mPendingInputMessages.begin());
    }
	// 如果在 尝试重新写入的过程中,把当前 buffer 也顺带处理了,那么就直接返回
    if (!mInputBufferIsDequeued.editItemAt(index)) {
    
    
        return true;
    }
	// 将 buffer 记录到 mDequeuedInputBuffers 中
    mDequeuedInputBuffers.push_back(index);
	// 尝试从 source 获取数据,填充数据,并送回 decoder
    onRequestInputBuffers();
    return true;
}

Dado que esto implica la adquisición de datos de origen y la escritura del búfer de entrada, y también considera los problemas de falla en la adquisición de datos y falla en la escritura de datos, el flujo de procesamiento del búfer de entrada parece ser más complicado, pero no nos preocupemos, demos un paso. análisis.

Primero, veamos qué hace handleAnInputBuffer (se omiten algunos comentarios):

  1. Determinar si el flujo de código se está procesando actualmente;
  2. Obtenga el índice correspondiente de MediaCodec MediaCodecBuffer;
  3. Registre el MediaCodecBuffer obtenido en la lista mInputBuffers según el índice;
  4. Cree una lista mInputBufferIsDequeuedpara registrar si el búfer de entrada correspondiente al índice está fuera de la cola;
  5. Cada vez que realizas una descarga, necesitas enviar el buffer csd al decodificador. Recuerda, ¡esto es siempre! De hecho, no todos los vaciados de decodificadores requieren un búfer csd. ¡Me han engañado antes!
  6. lidiar con la colcha primeroDemoraEl búfer de entrada, el motivo del retraso se analizará más adelante;
  7. Al procesar el búfer de retraso, se puede procesar el búfer de entrada actual.Si la posición correspondiente en la lista de registros fuera de la cola es falsa, significa que se ha procesado;
  8. Si el búfer de entrada no ha sido procesado, agréguelo a una lista sin procesar mDequeuedInputBuffers;
  9. Llame a onRequestInputBuffers para leer datos de la Fuente;

Hay cuatro métodos involucrados aquí y sus nombres son bastante similares. Primero introduzcamos para qué se utilizan:

  • onRequestInputBuffers: solicita datos de entrada de la fuente;
  • doRequestBuffers: implementación interna de onRequestInputBuffers;
  • onInputBufferFetched: obtiene datos de la fuente con éxito, los llena en el búfer de entrada y los devuelve a MediaCodec;
  • fetchInputData: implementación interna de onInputBufferFetched;

Comencemos con onRequestInputBuffers. Este método se implementa en DecoderBase y los permisos están protegidos:

void NuPlayer::DecoderBase::onRequestInputBuffers() {
    
    
	// 判断是否处在 处理不连续码流 的状态
    if (mRequestInputBuffersPending) {
    
    
        return;
    }

    // doRequestBuffers() return true if we should request more data
    // 从 Source 请求数据,如果失败返回 true,发送一条延时消息,retry
    if (doRequestBuffers()) {
    
    
    	// retry 时不会继续处理 获取数据的调用
        mRequestInputBuffersPending = true;

        sp<AMessage> msg = new AMessage(kWhatRequestInputBuffers, this);
        msg->post(10 * 1000LL);
    }
}

Puedes ver que onRequestInputBuffers encapsula doRequestBuffers, por lo que sus funciones son las mismas, pero una es un método de la clase base y la otra es un método de la subclase. ¿De qué sirve tal diseño? Tengo entendido que cada decodificador necesita obtener datos de la fuente, por lo que el método para obtener datos, onRequestInputBuffers, está definido en la clase base. Sin embargo, el método o proceso de obtención de datos para cada decodificador es diferente, por lo que doRequestBuffers se coloca en una subclase realizada en. La subclase llama al método onRequestInputBuffers de la clase principal para utilizar el proceso de lectura de datos definido por la clase principal y llama a la implementación de lectura de datos de la subclase durante el proceso, lo que mata dos pájaros de un tiro, unificando el proceso de lectura y diferenciando métodos de lectura.

Además, hay mRequestInputBuffersPendingotros usos que vale la pena aprender. Si no puede obtener datos de la Fuente, debe esperar un retraso e intentar obtenerlos nuevamente. Durante el proceso de espera, no queremos que el exterior llame a doRequestBuffers para obtener datos. , Entonces configure mRequestInputBuffersPending. Es verdadero, lo que indica el estado de espera. Este estado solo se puede liberar cuando se procesa el mensaje de reintento.

bool NuPlayer::Decoder::doRequestBuffers() {
    
    
    if (isDiscontinuityPending()) {
    
    
        return false;
    }
    status_t err = OK;
    while (err == OK && !mDequeuedInputBuffers.empty()) {
    
    
        size_t bufferIx = *mDequeuedInputBuffers.begin();
        sp<AMessage> msg = new AMessage();
        msg->setSize("buffer-ix", bufferIx);
        err = fetchInputData(msg);
        if (err != OK && err != ERROR_END_OF_STREAM) {
    
    
            // if EOS, need to queue EOS buffer
            break;
        }
        mDequeuedInputBuffers.erase(mDequeuedInputBuffers.begin());

        if (!mPendingInputMessages.empty()
                || !onInputBufferFetched(msg)) {
    
    
            mPendingInputMessages.push_back(msg);
        }
    }

    return err == -EWOULDBLOCK
            && mSource->feedMoreTSData() == OK;
}

Hay un bucle en doRequestBuffers que mDequeuedInputBuffersprocesará todos los búferes de entrada que se encuentran actualmente en él. Hay una pregunta aquí: ¿cuándo será mayor que 1 el número de buffers en mDequeuedInputBuffers? Cuando falla la lectura de datos de la fuente, regresará directamente y no se puede llamar al método de borrado. En este momento, el número de mDequeuedInputBuffers será mayor que 1.

status_t NuPlayer::Decoder::fetchInputData(sp<AMessage> &reply) {
    
    
    sp<ABuffer> accessUnit;
    bool dropAccessUnit = true;
    do {
    
    
    	// 从 Source 获取数据
        status_t err = mSource->dequeueAccessUnit(mIsAudio, &accessUnit);
		// 判断返回值,如果是 EWOULDBLOCK 那么说明没读到数据,如果是其他的返回值则说名读到数据
        if (err == -EWOULDBLOCK) {
    
    
            return err;
        } else if (err != OK) {
    
    
        	// 如果 error 不等于 OK,说明码流出现了一些情况
            if (err == INFO_DISCONTINUITY) {
    
    
                int32_t type;
                // 获取码流不连续的原因
                CHECK(accessUnit->meta()->findInt32("discontinuity", &type));

                bool formatChange =
                    (mIsAudio &&
                     (type & ATSParser::DISCONTINUITY_AUDIO_FORMAT))
                    || (!mIsAudio &&
                            (type & ATSParser::DISCONTINUITY_VIDEO_FORMAT));

                bool timeChange = (type & ATSParser::DISCONTINUITY_TIME) != 0;

                ALOGI("%s discontinuity (format=%d, time=%d)",
                        mIsAudio ? "audio" : "video", formatChange, timeChange);

                bool seamlessFormatChange = false;
                sp<AMessage> newFormat = mSource->getFormat(mIsAudio);
                // 如果是格式变化
                if (formatChange) {
    
    
                	// 判断当前播放的码流格式是否支持无缝切换
                    seamlessFormatChange =
                        supportsSeamlessFormatChange(newFormat);
                    // treat seamless format change separately
                    formatChange = !seamlessFormatChange;
                }

                // For format or time change, return EOS to queue EOS input,
                // then wait for EOS on output.
                // 如果不支持无缝切换,那么就要向 decoder 填充 eos
                if (formatChange /* not seamless */) {
    
    
                    mFormatChangePending = true;
                    err = ERROR_END_OF_STREAM;
                } else if (timeChange) {
    
    
                	// 如果pts不连续,那么就要向 decoder 填充 eos,恢复播放后要 发送 csd buffer
                    rememberCodecSpecificData(newFormat);
                    mTimeChangePending = true;
                    err = ERROR_END_OF_STREAM;
                } else if (seamlessFormatChange) {
    
    
                    // reuse existing decoder and don't flush
                    // 如果是无缝切换,那么仍要发送 csd buffer
                    rememberCodecSpecificData(newFormat);
                    continue;
                } else {
    
    
                    // This stream is unaffected by the discontinuity
                    return -EWOULDBLOCK;
                }
            }
            // reply should only be returned without a buffer set
            // when there is an error (including EOS)
            CHECK(err != OK);

            reply->setInt32("err", err);
            return ERROR_END_OF_STREAM;
        }
		// 以下是 drop 机制
        dropAccessUnit = false;
        if (!mIsAudio && !mIsEncrypted) {
    
    
			// 如果视频流慢了 100ms,视频为avc,并且不是参考帧,那么就drop掉当前读取的内容
            if (mRenderer->getVideoLateByUs() > 100000LL
                    && mIsVideoAVC
                    && !IsAVCReferenceFrame(accessUnit)) {
    
    
                dropAccessUnit = true;
            } 
            if (dropAccessUnit) {
    
    
                ++mNumInputFramesDropped;
            }
        }
    } while (dropAccessUnit);

    reply->setBuffer("buffer", accessUnit);

    return OK;
}

fetchInputData no solo obtiene datos, sino que también maneja excepciones en el flujo de código. dequeueAccessUnit tiene cuatro valores de retorno:

  • OK: Se obtienen datos válidos;
  • -EWOULDBLOCK: No se pudieron leer los datos;
  • INFO_DISCONTINUITY: el flujo de código es discontinuo;
  • ERROR_END_OF_STREAM: Leer hasta el final del archivo;

Los valores de retorno de OK y ERROR_END_OF_STREAM son normales; -EWOULDBLOCK regresará directamente e intentará volver a intentarlo; INFO_DISCONTINUITY indica que el flujo de código es discontinuo, lo que puede deberse a que se llama a selectTrack o seek.

Hay dos casos de discontinuidad del flujo de código:

  • flujo de códigoCambios de formato, el error es DISCONTINUITY_VIDEO_FORMAT, los cambios de formato son cambios de ancho y alto y cambios de tipo MIME, que pueden aparecer después de llamar a selectTrack;
  • flujo de códigolos pts no son consecutivos, el error DISCONTINUITY_TIMEpuede aparecer después de la llamada de búsqueda o cuando el punto retrocede al final de la reproducción de la transmisión.

Uno es el flujo de código.Cambios de formato DISCONTINUITY_VIDEO_FORMATEl lavado causado puede ocurrir al seleccionar Track; el otro esEnvoltura de puntos de flujo de código DISCONTINUITY_TIME, esta situación ocurre con más frecuencia en las repeticiones en vivo.

Si el formato cambia, se juzgará si el flujo de código reproducido actualmente es compatible 无缝切换(adaptive-playback). Si es compatible, el evento no se procesará. Si no es compatible, el valor de retorno se establecerá en ERROR_END_OF_STREAM.

Si los puntos son discontinuos, el valor de retorno se establecerá directamente en ERROR_END_OF_STREAM. Dado que ERROR_END_OF_STREAM está configurado, el búfer csd debe llenarse primero después de reiniciar la reproducción.

fetchInputData también diseñó un mecanismo de eliminación para la transmisión de código en formato AVC. Si la transmisión de video es 100 ms más lenta que el audio y el cuadro actual no es un cuadro de referencia, entonces el cuadro se eliminará.

Después de llamar exitosamente a fetchInputData, se debe llamar a onInputBufferFetched para completar los datos obtenidos en el búfer de entrada y enviarlos de regreso a MediaCodec. Esto es relativamente simple, simplemente copie los datos y lo único que debe mirar es EOS. Hay dos situaciones en EOS: una es que el búfer está vacío, lo que indica que se ha recibido ERROR_END_OF_STREAM, la otra es que el búfer no está vacío y el valor de retorno es correcto, pero hay información eos en bufferMeta.

Si el flujo de código finaliza, fetchInputData no leerá ningún dato después de enviar la información eos.

Si se envía eos porque el flujo de código es discontinuo, isDiscontinuityPending interrumpirá el flujo de procesamiento del búfer de entrada.Espere hasta que los datos anteriores se decodifiquen y representen, y luego maneje el evento Discontinuidad., los datos de la siguiente secuencia se escribirán una vez que se complete el procesamiento. Veremos esta parte en la siguiente sección.

bool NuPlayer::Decoder::isDiscontinuityPending() const {
    
    
    return mFormatChangePending || mTimeChangePending;
}

3.2、CB_OUTPUT_AVAILABLE

El flujo de procesamiento del búfer de salida es mucho más simple que el de la entrada y llama principalmente al método handleAnOutputBuffer:

bool NuPlayer::Decoder::handleAnOutputBuffer(
        size_t index,
        size_t offset,
        size_t size,
        int64_t timeUs,
        int32_t flags) {
    
    
    sp<MediaCodecBuffer> buffer;
    // 获取 output buffer
    mCodec->getOutputBuffer(index, &buffer);

    int64_t frameIndex;
    bool frameIndexFound = buffer->meta()->findInt64("frameIndex", &frameIndex);

    buffer->setRange(offset, size);
    // 设置 pts
    buffer->meta()->clear();
    buffer->meta()->setInt64("timeUs", timeUs);
    if (frameIndexFound) {
    
    
        buffer->meta()->setInt64("frameIndex", frameIndex);
    }
	// 判断 output buffer 是否到达 eos
    bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
    // we do not expect CODECCONFIG or SYNCFRAME for decoder

	// 创建 reply,设置 generation,avsync完成后 renderer 通过该消息 callback 回来
    sp<AMessage> reply = new AMessage(kWhatRenderBuffer, this);
    reply->setSize("buffer-ix", index);
    reply->setInt32("generation", mBufferGeneration);
    reply->setSize("size", size);
	// 如果出现 eos 则在 reply 中也进行标记
    if (eos) {
    
    
        ALOGV("[%s] saw output EOS", mIsAudio ? "audio" : "video");

        buffer->meta()->setInt32("eos", true);
        reply->setInt32("eos", true);
    }

    mNumFramesTotal += !mIsAudio;
	// 判断 input buffer 有没有设定起播时间
    if (mSkipRenderingUntilMediaTimeUs >= 0) {
    
    
        if (timeUs < mSkipRenderingUntilMediaTimeUs) {
    
    
            ALOGV("[%s] dropping buffer at time %lld as requested.",
                     mComponentName.c_str(), (long long)timeUs);

            reply->post();
            if (eos) {
    
    
                notifyResumeCompleteIfNecessary();
                if (mRenderer != NULL && !isDiscontinuityPending()) {
    
    
                    mRenderer->queueEOS(mIsAudio, ERROR_END_OF_STREAM);
                }
            }
            return true;
        }
        mSkipRenderingUntilMediaTimeUs = -1;
    }

    // wait until 1st frame comes out to signal resume complete
    // 播放停止后重新恢复播放,等待第一帧到达后上抛消息,在seek时用到
    notifyResumeCompleteIfNecessary();

    if (mRenderer != NULL) {
    
    
        // send the buffer to renderer.
        // 将 ouput buffer 送到 renderer 做 avsync
        mRenderer->queueBuffer(mIsAudio, buffer, reply);
        // 如果到达 eos,并且不是因为码流中断,调用queueEOS
        if (eos && !isDiscontinuityPending()) {
    
    
            mRenderer->queueEOS(mIsAudio, ERROR_END_OF_STREAM);
        }
    }

    return true;
}
  1. Obtener búfer de salida;
  2. Cree una respuesta, configure la generación y, una vez que se complete avsync, el Renderer vuelve a llamar al Decoder a través de este mensaje;
  3. Determine si el indicador del búfer de salida es eos y, de ser así, márquelo en la respuesta;
  4. El búfer de entrada de la cola puede tener puntos para comenzar a renderizar. Si los puntos del búfer de salida son más pequeños que los puntos, se eliminará directamente;
  5. Envíe el búfer de salida y el mensaje de respuesta al Renderer juntos. Si se alcanza eos, y no es porque el flujo de código sea discontinuo, se enviará un EOS al Renderer;

3.3、CB_OUTPUT_FORMAT_CHANGED

Aunque pasaremos el formato de entrada cuando usemos el decodificador, el decodificador aún analizará el formato por sí mismo después de recibir los datos y generará el evento de cambio de formato de salida. La capa superior debe realizar el procesamiento correspondiente después de recibir el evento.

void NuPlayer::Decoder::handleOutputFormatChange(const sp<AMessage> &format) {
    
    
    if (!mIsAudio) {
    
    
        int32_t width, height;
        if (format->findInt32("width", &width)
                && format->findInt32("height", &height)) {
    
    
            Mutex::Autolock autolock(mStatsLock);
            mStats->setInt32("width", width);
            mStats->setInt32("height", height);
        }
        sp<AMessage> notify = mNotify->dup();
        notify->setInt32("what", kWhatVideoSizeChanged);
        notify->setMessage("format", format);
        notify->post();
    } else if (mRenderer != NULL) {
    
    
        uint32_t flags;
        int64_t durationUs;
        bool hasVideo = (mSource->getFormat(false /* audio */) != NULL);
        if (getAudioDeepBufferSetting() // override regardless of source duration
                || (mSource->getDuration(&durationUs) == OK
                        && durationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US)) {
    
    
            flags = AUDIO_OUTPUT_FLAG_DEEP_BUFFER;
        } else {
    
    
            flags = AUDIO_OUTPUT_FLAG_NONE;
        }

        sp<AMessage> reply = new AMessage(kWhatAudioOutputFormatChanged, this);
        reply->setInt32("generation", mBufferGeneration);
        mRenderer->changeAudioFormat(
                format, false /* offloadOnly */, hasVideo,
                flags, mSource->isStreaming(), reply);
    }
}

Si el formato del vídeo cambia, simplemente continúa publicando el evento. Si el formato de audio cambia, el decodificador debe llamar a Renderer.changeAudioFormat para volver a abrir AudioTrack. Cómo manejar esto se presentará brevemente en el artículo sobre Renderer.

3.4、kWhatRenderBuffer

En la sección anterior, mencionamos que después de que Renderer complete avsync, volverá a llamar a Decoder en forma de mensaje:

        case kWhatRenderBuffer:
        {
    
    
            if (!isStaleReply(msg)) {
    
    
                onRenderBuffer(msg);
            }
            break;
        }

Ya hemos explicado isStaleReply arriba, por lo que no entraremos en detalles aquí. Veamos principalmente onRenderBuffer:

void NuPlayer::Decoder::onRenderBuffer(const sp<AMessage> &msg) {
    
    
    status_t err;
    int32_t render;
    size_t bufferIx;
    int32_t eos;
    size_t size;
    // 查找要渲染的output buffer index
    CHECK(msg->findSize("buffer-ix", &bufferIx));

    if (mCodec == NULL) {
    
    
        err = NO_INIT;
    } else if (msg->findInt32("render", &render) && render) {
    
     // 判断是否render
        int64_t timestampNs;
        CHECK(msg->findInt64("timestampNs", &timestampNs));	// 获取render时间
        err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs);
    } else {
    
    
    	// 如果是 eos 或者 不render 则直接 drop
        if (!msg->findInt32("eos", &eos) || !eos ||
                !msg->findSize("size", &size) || size) {
    
    
            mNumOutputFramesDropped += !mIsAudio;
        }
        err = mCodec->releaseOutputBuffer(bufferIx);
    }
	// 如果是因为码流不连续造成的eos,则处理不连续事件
    if (msg->findInt32("eos", &eos) && eos
            && isDiscontinuityPending()) {
    
    
        finishHandleDiscontinuity(true /* flushOnTimeChange */);
    }
}

onRenderBuffer se utiliza principalmente para procesar video. El renderizador determina que el fotograma debe renderizarse y luego llama a renderOutputBufferAndRelease; de ​​lo contrario, llama a releaseOutputBuffer.

Si el mensaje de respuesta contiene eos, se determinará si el eos se debe a la discontinuidad del flujo de código. Lo que debemos tener en cuenta es que cuando Renderer realmente ejecuta EOS, los eventos no se enviarán a Decoder, Decoder solo procesa eventos de búfer.

Aquí volvemos a ver cómo FinishHandleDiscontinuity maneja las excepciones de flujo de código:

void NuPlayer::Decoder::finishHandleDiscontinuity(bool flushOnTimeChange) {
    
    
    ALOGV("finishHandleDiscontinuity: format %d, time %d, flush %d",
            mFormatChangePending, mTimeChangePending, flushOnTimeChange);

    // If we have format change, pause and wait to be killed;
    // If we have time change only, flush and restart fetching.

    if (mFormatChangePending) {
    
    
        mPaused = true;
    } else if (mTimeChangePending) {
    
    
        if (flushOnTimeChange) {
    
    
            doFlush(false /* notifyComplete */);
            signalResume(false /* notifyComplete */);
        }
    }

    // Notify NuPlayer to either shutdown decoder, or rescan sources
    sp<AMessage> msg = mNotify->dup();
    msg->setInt32("what", kWhatInputDiscontinuity);
    msg->setInt32("formatChange", mFormatChangePending);
    msg->post();

    mFormatChangePending = false;
    mTimeChangePending = false;
}

De los comentarios, podemos ver que el cambio de formato y el cambio de hora se manejan de manera diferente:

  • cambio de formato: pausar el proceso de procesamiento del búfer y esperar a que se reinicie el decodificador;
  • cambio de hora: enjuague y luego llame al currículum para restaurar;

Hay una pausa mencionada en el cambio de formato. Si mPaused se establece en verdadero, onMessageReceived ya no procesará el búfer enviado. Cabe señalar que esta pausa no se . El evento de cambio de formato debe enviarse a NuPlayer:

            if (what == DecoderBase::kWhatInputDiscontinuity) {
    
    
                int32_t formatChange;
                CHECK(msg->findInt32("formatChange", &formatChange));

                ALOGV("%s discontinuity: formatChange %d",
                        audio ? "audio" : "video", formatChange);

                if (formatChange) {
    
    
                    mDeferredActions.push_back(
                            new FlushDecoderAction(
                                audio ? FLUSH_CMD_SHUTDOWN : FLUSH_CMD_NONE,
                                audio ? FLUSH_CMD_NONE : FLUSH_CMD_SHUTDOWN));
                }

                mDeferredActions.push_back(
                        new SimpleAction(
                                &NuPlayer::performScanSources));

                processDeferredActions();
            } 

NuPlayer ejecutará FlushDecoderAction, realizará el apagado para liberar el decodificador actual y luego llamará a performScanSources nuevamente para crear un decodificador para el nuevo formato.

4 、 señal de descarga

void NuPlayer::Decoder::doFlush(bool notifyComplete) {
    
    
    if (mCCDecoder != NULL) {
    
    
        mCCDecoder->flush();
    }

    if (mRenderer != NULL) {
    
    
        mRenderer->flush(mIsAudio, notifyComplete);
        mRenderer->signalTimeDiscontinuity();
    }

    status_t err = OK;
    if (mCodec != NULL) {
    
    
        err = mCodec->flush();
        mCSDsToSubmit = mCSDsForCurrentFormat; // copy operator
        ++mBufferGeneration;
    }

    if (err != OK) {
    
    
        ALOGE("failed to flush [%s] (err=%d)", mComponentName.c_str(), err);
        handleError(err);
        // finish with posting kWhatFlushCompleted.
        // we attempt to release the buffers even if flush fails.
    }
    releaseAndResetMediaBuffers();
    mPaused = true;
}

void NuPlayer::Decoder::onFlush() {
    
    
    doFlush(true);

    if (isDiscontinuityPending()) {
    
    
        // This could happen if the client starts seeking/shutdown
        // after we queued an EOS for discontinuities.
        // We can consider discontinuity handled.
        finishHandleDiscontinuity(false /* flushOnTimeChange */);
    }

    sp<AMessage> notify = mNotify->dup();
    notify->setInt32("what", kWhatFlushCompleted);
    notify->post();
}

Flush es relativamente simple, así que no diré tonterías. El trabajo principal es llamar al vaciado del Renderer, restablecer el estado del Renderer, llamar al vaciado de MediaCodec, actualizar el búfer de entrada y el búfer de salida. Tenga en cuenta que esta llamada al método modificará mBufferGeneration y finalmente borrará la lista de búfer almacenada en el Decodificador.

Vimos en la sección anterior que se llamó a doFlush en FinishHandleDiscontinuity, por lo que no se enviará ningún evento kWhatFlushCompleted a NuPlayer.

5、iniciarApagar

void NuPlayer::Decoder::onShutdown(bool notifyComplete) {
    
    
    status_t err = OK;

    // if there is a pending resume request, notify complete now
    notifyResumeCompleteIfNecessary();

    if (mCodec != NULL) {
    
    
    	// 释放decoder
        err = mCodec->release();
        // 释放 MediaCodec
        mCodec = NULL;
        // 修改 generation 阻止渲染
        ++mBufferGeneration;

        if (mSurface != NULL) {
    
    
            // reconnect to surface as MediaCodec disconnected from it
            status_t error = nativeWindowConnect(mSurface.get(), "onShutdown");
            ALOGW_IF(error != NO_ERROR,
                    "[%s] failed to connect to native window, error=%d",
                    mComponentName.c_str(), error);
        }
        mComponentName = "decoder";
    }
	// 释放 buffer list
    releaseAndResetMediaBuffers();

    if (err != OK) {
    
    
        ALOGE("failed to release [%s] (err=%d)", mComponentName.c_str(), err);
        handleError(err);
        // finish with posting kWhatShutdownCompleted.
    }

    if (notifyComplete) {
    
    
        sp<AMessage> notify = mNotify->dup();
        notify->setInt32("what", kWhatShutdownCompleted);
        notify->post();
        // 停止处理 buffer 事件
        mPaused = true;
    }
}

El apagado también es muy simple:

  1. Llame al método de liberación de MediaCodec para liberar el decodificador;
  2. Libere el objeto MediaCodec;
  3. Modificar la generación y detener el procesamiento de eventos de renderizado;
  4. Lista de búfer de liberación;
  5. Establezca mPaused en verdadero para detener el procesamiento de eventos del búfer;

6 、 señalReanudar

void NuPlayer::Decoder::onResume(bool notifyComplete) {
    
    
    mPaused = false;

    if (notifyComplete) {
    
    
        mResumePending = true;
    }

    if (mCodec == NULL) {
    
    
        ALOGE("[%s] onResume without a valid codec", mComponentName.c_str());
        handleError(NO_INIT);
        return;
    }
    mCodec->start();
}

Después del vaciado, se debe llamar a signalResume para iniciar el proceso de decodificación de recuperación de MediaCodec. El núcleo es llamar al método de inicio de MediaCodec. El mResumePending aquí se usa para determinar si kWhatResumeCompleted debe enviarse a NuPlayer cuando el decodificador envía el búfer de salida del primer cuadro.

7. Resumen

Nada más. Tengo entendido que no entiendo el mecanismo de Android ALooper lo suficientemente profundo, ni conozco los patrones de diseño. ¡Continuaré fortaleciendo esta parte de mi estudio en el futuro!

Si hay algún error en el contenido anterior, no dude en brindarme orientación.

Si lo encuentra útil, no dude en darle me gusta, recopilarlo y seguirlo. Su apoyo es mi mayor motivación para actualizar.

Si desea leer otro contenido del marco de Android Media, vaya también a https://blog.csdn.net/qq_41828351?spm=1000.2115.3001.5343

Je suppose que tu aimes

Origine blog.csdn.net/qq_41828351/article/details/132589796
conseillé
Classement