Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  encoder.c   Sprache: C

 

/*****************************************************************************
 * videotoolbox/encoder.c: Video Toolbox encoder
 *****************************************************************************
 * Copyright © 2023 VideoLabs
 *
 * Authors: Alexandre Janniaux <ajanni@videolabs.io>
 *          Marvin Scholz <epirat07 at gmail dot com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/


#ifdef HAVE_CONFIG_H
# import "config.h"
#endif

#import <vlc_common.h>
#import <vlc_plugin.h>
#import <vlc_codec.h>
#import "../hxxx_helper.h"
#import <vlc_bits.h>
#import <vlc_threads.h>
#import "../vt_utils.h"
#import "../../packetizer/h264_nal.h"
#import "../../packetizer/h264_slice.h"
#import "../../packetizer/hxxx_nal.h"
#import "../../packetizer/hxxx_sei.h"

#import <VideoToolbox/VideoToolbox.h>
#import <VideoToolbox/VTErrors.h>

#import <CoreFoundation/CoreFoundation.h>
#import <CoreVideo/CoreVideo.h>
#import <CoreMedia/CMTime.h>
#import <TargetConditionals.h>

#import <sys/types.h>
#import <sys/sysctl.h>
#import <mach/machine.h>

#pragma mark Encoder submodule

typedef struct encoder_sys_t
{
    VTCompressionSessionRef session;
    CMTime lastDate;
    bool isDraining;
    vlc_fifo_t *fifo;
    bool header;

    int NALUnitHeaderLengthOut;
} encoder_sys_t;

static block_t *EncodeCallback(encoder_t *enc, picture_t *pic)
{
    encoder_sys_t *sys = enc->p_sys;

    /* If we're draining, we have nothing to push. */
    sys->isDraining |= pic == NULL;
    if (!pic)
        goto return_block;

    picture_Hold(pic);

    CVPixelBufferRef buffer = cvpxpic_get_ref(pic);
    CMTime pts = CMTimeMake(pic->date, CLOCK_FREQ);
    CMTime duration = kCMTimeInvalid;
    VTEncodeInfoFlags infoFlags;

    OSStatus ret = VTCompressionSessionEncodeFrame(sys->session,
        buffer, pts, duration,
        NULL,
        pic,
        &infoFlags);
    /* VTCompressionSessionEncodeFrame can return error if the parameters
     * given above are not correct, like kVTParameterErr if session is NULL
     * so ensure the core is correct but don't overcheck in release. */

    assert(ret == noErr);

return_block:
    /* If we're draining, we wait for the encoder to output every other
     * block and gather them together before returning. */

    if (sys->isDraining)
        VTCompressionSessionCompleteFrames(sys->session, kCMTimeInvalid);

    /* Dequeue all available blocks. */
    vlc_fifo_Lock(sys->fifo);
    block_t *block = vlc_fifo_DequeueAllUnlocked(sys->fifo);
    vlc_fifo_Unlock(sys->fifo);

    if (pic)
        sys->lastDate = CMTimeMake(pic->date, CLOCK_FREQ);

    vlc_fifo_Signal(sys->fifo);

    return block;
}

static vlc_tick_t vlc_CMTime_to_tick(CMTime timestamp)
{
    CMTime scaled = CMTimeConvertScale(
            timestamp, CLOCK_FREQ,
            kCMTimeRoundingMethod_Default);

    return VLC_TICK_0 + scaled.value;
}

static int PushBlockUnlocked(encoder_t *enc, CMSampleBufferRef sampleBuffer)
{
    static const uint8_t startcode[] = {0x00, 0x00, 0x00, 0x01};
    encoder_sys_t *sys = enc->p_sys;

    CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    CMTime dts = CMSampleBufferGetDecodeTimeStamp(sampleBuffer);

    vlc_tick_t vlc_pts = vlc_CMTime_to_tick(pts);
    /* Note: dts can be !CMTIME_IS_VALID() when there are B-frames.
     * We need to reorder the frames when it happens, but B-frames are
     * disabled for now. */

    vlc_tick_t vlc_dts = CMTIME_IS_VALID(dts) ?
        vlc_CMTime_to_tick(dts) : vlc_pts;

    CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer(sampleBuffer);

    size_t length = CMBlockBufferGetDataLength(buffer);
    size_t read;
    size_t offset = 0;

    CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0);
    CFDictionaryRef properties = nil;

    /* Frames are considered to be IDR frames by default:
     * https://developer.apple.com/documentation/coremedia/kcmsampleattachmentkey_notsync */

    Boolean isIDR = TRUE;

    /* We need this attachments array to extract metadata. */
    if (attachments == NULL || CFArrayGetCount(attachments) == 0)
        goto parse_block;

    /* Metadata parsing:
     * We need to check whether the frame is an IDR frame or not
     * in order to know whether we need to inject the SPS/PPS
     * NAL units in the stream. */

    properties = CFArrayGetValueAtIndex(attachments, 0);

    CFBooleanRef isNotSync;
    if (CFDictionaryGetValueIfPresent(properties,
                                      kCMSampleAttachmentKey_NotSync,
                                      (const void**)&isNotSync))
    {
        /* If the attachment signal that it's not a sync frame,
         * it means that it's not an IDR frame. */

        isIDR = !CFBooleanGetValue(isNotSync);
    }

    block_t *header = NULL;
    if (isIDR && !sys->header)
    {
        CMFormatDescriptionRef description =
            CMSampleBufferGetFormatDescription(sampleBuffer);

        const uint8_t *sps, *pps;
        size_t sps_length, pps_length;

        OSStatus status;
        int NALUnitHeaderLengthOut;
        /* The following function will already handle emulation prevention bytes. */
        status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(description,
                0, &sps, &sps_length, NULL, &NALUnitHeaderLengthOut);
        /* This should only fail here if we gave the wrong values above */
        assert(status == noErr);
        sys->NALUnitHeaderLengthOut = NALUnitHeaderLengthOut;
        status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(description,
                1, &pps, &pps_length, NULL, NULL);
        /* This should only fail here if we gave the wrong values above */
        assert(status == noErr);

        assert(sps != NULL && pps != NULL);
        assert(sps_length < (1 << 16));
        assert(pps_length < (1 << 16));

        size_t i_extra = sps_length + pps_length + sizeof(startcode) * 2;
        header = block_Alloc(i_extra);
        if (header == NULL){} // TODO

        size_t hdroffset = 0;
        memcpy(&header->p_buffer[hdroffset], startcode, sizeof(startcode));
        hdroffset += sizeof(startcode);

        memcpy(&header->p_buffer[hdroffset], sps, sps_length);
        hdroffset += sps_length;

        memcpy(&header->p_buffer[hdroffset], startcode, sizeof(startcode));
        hdroffset += sizeof(startcode);

        memcpy(&header->p_buffer[hdroffset], pps, pps_length);
        header->i_buffer = i_extra;
        sys->header = true;
    }

parse_block:

    while (offset != length)
    {
        OSStatus status;
        char *cursor_ptr;
        status = CMBlockBufferGetDataPointer(buffer, offset, &read, NULL, &cursor_ptr);
        uint8_t *cursor = (uint8_t *)cursor_ptr;

        /* Did we used an invalid CMBlockBuffer from invalid offset somehow? */
        assert(status == kCMBlockBufferNoErr);

        

        hxxx_iterator_ctx_t hh;
        hxxx_iterator_init(&hh, cursor, read, sys->NALUnitHeaderLengthOut);
        const uint8_t *hh_start;
        size_t hh_size, block_size = 0, block_offset = 0;
        while (hxxx_iterate_next(&hh, &hh_start, &hh_size))
            block_size += sizeof(startcode) + hh_size;

        offset += read;
        block_t *block = block_Alloc(block_size);

        hxxx_iterator_init(&hh, cursor, read, sys->NALUnitHeaderLengthOut);

      /* Extract the avcC block size and copy each NAL to the same block.
       * Multiple NAL can be present in different situation, like I-frame
       * with SEI data. */

        while (hxxx_iterate_next(&hh, &hh_start, &hh_size))
        {
            memcpy(&block->p_buffer[block_offset], startcode, sizeof startcode);
            memcpy(&block->p_buffer[block_offset + sizeof startcode], hh_start, hh_size);
            block_offset += sizeof(startcode) + hh_size;
        }
        block->i_pts = vlc_pts;
        block->i_dts = vlc_dts;
        block->i_flags = isIDR ? BLOCK_FLAG_TYPE_I : 0;
        block->i_flags |= BLOCK_FLAG_AU_END;

        if (header)
        {
            header->p_next = block;
            block = block_ChainGather(header);
            assert(block != NULL);
        }
        vlc_fifo_QueueUnlocked(sys->fifo, block);
    }

    return VLC_SUCCESS;
}

static OSStatus PushEachBlockUnlocked(CMSampleBufferRef sampleBuffer,
        CMItemCount index, void *opaque)
{
    encoder_t *encoder = opaque;
    (void)index;

    PushBlockUnlocked(encoder, sampleBuffer);
    return noErr;
}

static void EncoderOutputCallback(void *cookie,
    void *sourceFrameRefCon, OSStatus status,
    VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer)
{
    VLC_UNUSED(infoFlags);

    encoder_t *encoder = cookie;
    encoder_sys_t *sys = encoder->p_sys;

    /* Nothing doable here. */
    if (status != noErr)
        return;

    picture_t *pic = sourceFrameRefCon;

    picture_Release(pic);
    vlc_fifo_Lock(sys->fifo);
    CMSampleBufferCallForEachSample(sampleBuffer, PushEachBlockUnlocked, encoder);
    vlc_fifo_Unlock(sys->fifo);
}

static void CloseEncoder(encoder_t *encoder)
{
    encoder_sys_t *p_sys = encoder->p_sys;

    VTCompressionSessionCompleteFrames(p_sys->session, kCMTimeInvalid);
    VTCompressionSessionInvalidate(p_sys->session);

    block_FifoRelease(p_sys->fifo);

    CFRelease(p_sys->session);
    p_sys->session = NULL;
}

static int OpenEncoder(vlc_object_t *obj)
{
    encoder_t *enc = (encoder_t *)obj;
    encoder_sys_t *sys;

    if (enc->fmt_out.i_codec != VLC_CODEC_H264)
        return VLC_EGENERIC;

    sys = vlc_obj_malloc(obj, sizeof *sys);
    if (sys == NULL)
        return VLC_ENOMEM;

    sys->fifo = block_FifoNew();
    if (sys->fifo == NULL)
        return VLC_ENOMEM;
    sys->lastDate = CMTimeMake(0, CLOCK_FREQ);
    sys->header = false;

    OSStatus ret = VTCompressionSessionCreate(NULL,
        enc->fmt_in.video.i_visible_width,
        enc->fmt_in.video.i_visible_height,
        kCMVideoCodecType_H264,
        NULL,
        NULL,
        NULL,
        EncoderOutputCallback,
        enc,
        &sys->session);

    if (ret != noErr)
        goto error_session;

    ret = VTSessionSetProperty(sys->session,
            kVTCompressionPropertyKey_AllowFrameReordering,
            kCFBooleanFalse);

    if (ret != noErr)
        goto error_property;

    static const struct vlc_encoder_operations ops =
    {
        .encode_video = EncodeCallback,
        .close = CloseEncoder,
    };
    enc->p_sys = sys;
    enc->ops = &ops;

    switch (enc->fmt_in.i_codec)
    {
        case VLC_CODEC_CVPX_NV12:
        case VLC_CODEC_CVPX_BGRA:
        case VLC_CODEC_CVPX_UYVY:
        case VLC_CODEC_CVPX_P010:
        case VLC_CODEC_CVPX_I420:
            break;

        default:
            /* Note: QuickTime prefers I420 rather than NV12. */
            enc->fmt_in.i_codec = VLC_CODEC_CVPX_I420;
    }

    video_format_Copy(&enc->fmt_out.video, &enc->fmt_in.video);
    enc->fmt_out.video.i_frame_rate =
        enc->fmt_in.video.i_frame_rate;
    enc->fmt_out.video.i_frame_rate_base =
        enc->fmt_in.video.i_frame_rate_base;
    enc->fmt_out.i_codec = VLC_CODEC_H264;

    msg_Info(enc, "Videotoolbox encoding %4.4s framerate %d/%d size %dx%d",
             (const char *)&enc->fmt_out.i_codec,
             enc->fmt_in.video.i_frame_rate,
             enc->fmt_in.video.i_frame_rate_base,
             enc->fmt_in.video.i_visible_width,
             enc->fmt_in.video.i_visible_height);

    return VLC_SUCCESS;
error_property:
    VTCompressionSessionInvalidate(sys->session);
    CFRelease(sys->session);
error_session:
    block_FifoRelease(sys->fifo);
    return VLC_EGENERIC;
}

#pragma mark - Module descriptor

vlc_module_begin()
    set_section(N_("Encoding") , NULL)
    set_subcategory(SUBCAT_INPUT_VCODEC)
    set_description(N_("VideoToolbox video encoder"))
    set_capability("video encoder", 1000)
    set_callback(OpenEncoder)
vlc_module_end()

Messung
CHZG
839710093

¤ Dauer der Verarbeitung: 0.5 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104845
#Domains=499261