All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.red5.codec.AV1Video Maven / Gradle / Ivy

There is a newer version: 2.0.15
Show newest version
/*
 * RED5 Open Source Media Server - https://github.com/Red5/ Copyright 2006-2022 by respective authors (see below). All rights reserved. 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.
 */

package org.red5.codec;

import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.mina.core.buffer.IoBuffer;
import org.red5.io.IoConstants;
import org.red5.util.ByteNibbler;

/**
 * Red5 video codec for the AV1 video format. Portions of this AV1 code are based on the work of the Pion project.
 *
 * @author The Red5 Project
 * @author Paul Gregoire ([email protected])
 */
public class AV1Video extends AbstractVideo {

    /* AV1
       https://aomediacodec.github.io/av1-isobmff/v1.3.0.html#av1codecconfigurationbox-definition

       class AV1CodecConfigurationBox extends Box('av1C')
        {
        AV1CodecConfigurationRecord av1Config;
        }

        aligned(8) class AV1CodecConfigurationRecord {
            unsigned int(1) marker = 1;
            unsigned int(7) version = 1;
            unsigned int(3) seq_profile;
            unsigned int(5) seq_level_idx_0;
            unsigned int(1) seq_tier_0;
            unsigned int(1) high_bitdepth;
            unsigned int(1) twelve_bit;
            unsigned int(1) monochrome;
            unsigned int(1) chroma_subsampling_x;
            unsigned int(1) chroma_subsampling_y;
            unsigned int(2) chroma_sample_position;
            unsigned int(3) reserved = 0;

            unsigned int(1) initial_presentation_delay_present;
            if (initial_presentation_delay_present) {
                unsigned int(4) initial_presentation_delay_minus_one;
            } else {
                unsigned int(4) reserved = 0;
            }

            unsigned int(8) configOBUs[];
        }
    */
    private FrameData decoderConfiguration;

    {
        codec = VideoCodec.AV1;
    }

    /** {@inheritDoc} */
    @Override
    public boolean canDropFrames() {
        return true;
    }

    /** {@inheritDoc} */
    @SuppressWarnings("incomplete-switch")
    @Override
    public boolean addData(IoBuffer data, int timestamp) {
        log.trace("{} addData timestamp: {} remaining: {} pos: {}", codec.name(), timestamp, data.remaining(), data.position());
        boolean result = false;
        // go back to the beginning, this only works in non-multitrack scenarios
        if (data.position() > 0) {
            data.rewind();
        }
        // no data, no operation
        if (data.hasRemaining()) {
            // mark the position before we get the flags
            data.mark();
            // get the first byte for v1 codec type or enhanced codec bit
            byte flg = data.get();
            // determine if we've got an enhanced codec
            enhanced = ByteNibbler.isBitSet(flg, 7);
            // for frame type we need get 3 bits
            int ft = ((flg & 0b01110000) >> 4);
            frameType = VideoFrameType.valueOf(ft);
            if (enhanced) {
                // get the packet type
                packetType = VideoPacketType.valueOf(flg & IoConstants.MASK_VIDEO_CODEC);
                // get the fourcc
                int fourcc = data.getInt();
                // reset back to the beginning after we got the fourcc
                data.reset();
                if (isDebug) {
                    log.debug("{} - frame type: {} packet type: {}", VideoCodec.valueOfByFourCc(fourcc), frameType, packetType);
                }
                switch (packetType) {
                    case SequenceStart:
                        if (frameType == VideoFrameType.KEYFRAME) {
                            if (isDebug) {
                                log.debug("Decoder configuration");
                            }
                            // Store AV1 DecoderConfigurationRecord data, if one exists
                            if (decoderConfiguration == null) {
                                decoderConfiguration = new FrameData(data);
                            } else {
                                decoderConfiguration.setData(data);
                            }
                            // new sequence, clear keyframe and interframe collections
                            softReset();
                        }
                        break;
                    case CodedFramesX: // pass coded data without comp time offset
                        switch (frameType) {
                            case KEYFRAME: // keyframe
                                if (isDebug) {
                                    log.debug("Keyframe - keyframeTimestamp: {}", keyframeTimestamp);
                                }
                                // get the time stamp and compare with the current value
                                if (timestamp != keyframeTimestamp) {
                                    //log.trace("New keyframe");
                                    // new keyframe
                                    keyframeTimestamp = timestamp;
                                    // if its a new keyframe, clear keyframe and interframe collections
                                    softReset();
                                }
                                // store keyframe
                                keyframes.add(new FrameData(data));
                                break;
                            case INTERFRAME:
                                if (bufferInterframes) {
                                    if (isDebug) {
                                        log.debug("Interframe - timestamp: {}", timestamp);
                                    }
                                    if (interframes == null) {
                                        interframes = new CopyOnWriteArrayList<>();
                                    }
                                    try {
                                        int lastInterframe = numInterframes.getAndIncrement();
                                        //log.trace("Buffering interframe #{}", lastInterframe);
                                        if (lastInterframe < interframes.size()) {
                                            interframes.get(lastInterframe).setData(data);
                                        } else {
                                            interframes.add(new FrameData(data));
                                        }
                                    } catch (Throwable e) {
                                        log.warn("Failed to buffer interframe", e);
                                    }
                                    //log.trace("Interframes: {}", interframes.size());
                                }
                                break;
                        }
                        break;
                    case CodedFrames: // pass coded data
                        int compTimeOffset = (data.get() << 16 | data.get() << 8 | data.get());
                        switch (frameType) {
                            case KEYFRAME: // keyframe
                                if (isDebug) {
                                    log.debug("Keyframe - keyframeTimestamp: {} compTimeOffset: {}", keyframeTimestamp, compTimeOffset);
                                }
                                keyframes.add(new FrameData(data, compTimeOffset));
                                break;
                        }
                        break;
                }
            } else {
                // no non-enhanced codec suspport yet
            }
            //log.trace("Keyframes: {}", keyframes.size());
            // we handled the data
            result = true;
        }
        // reset the position
        data.rewind();
        return result;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy