深入理解VideoToolbox:iOS/macOS视频硬编解码实战指南

引言:VideoToolbox框架概述

VideoToolbox 是Apple提供的底层框架,首次在WWDC2014上推出,为iOS和macOS开发者提供直接访问硬件编码器和解码器的能力。作为Core Media框架的重要组成部分,VideoToolbox专注于视频压缩、解压缩以及CoreVideo像素缓冲区之间的格式转换,以Core Foundation (CF)类型的会话对象形式提供服务。

与AVFoundation等高层框架不同,VideoToolbox专为需要直接硬件访问的场景设计,适用于对性能要求严苛的应用,如实时视频通信、专业视频编辑和高分辨率媒体处理。对于不需要直接硬件控制的应用,Apple建议使用AVFoundation等更高级的框架。

支持平台与系统要求

VideoToolbox支持以下Apple平台:

  • iOS 8.0+:iPhone、iPad和iPod touch设备
  • macOS 10.8+:Macintosh计算机
  • tvOS 10.2+:Apple TV设备
  • visionOS 1.0+:Apple Vision Pro

框架采用硬件加速设计,充分利用Apple芯片中的媒体引擎,包括A系列芯片中的专用编解码模块和Apple Silicon的媒体处理单元(MPU),实现高效的视频处理。

核心架构与功能组件

框架核心组件

VideoToolbox提供三种主要会话类型,构成其核心架构:

  1. VTCompressionSession:视频编码会话,负责将原始视频数据压缩为H.264/HEVC等格式
  2. VTDecompressionSession:视频解码会话,负责将压缩视频数据解码为原始像素缓冲区
  3. VTPixelTransferSession:像素转换会话,处理不同像素格式之间的转换

这些会话对象通过属性键值对进行配置,支持细粒度的参数调整,以满足不同应用场景需求。

支持的编解码格式

VideoToolbox支持多种视频编解码格式:

编码支持

  • H.264/AVC (所有支持平台)
  • HEVC/H.265 (iOS 11+/macOS 10.13+)
  • ProRes (macOS)

解码支持

  • H.263、H.264、HEVC
  • MPEG-1、MPEG-2、MPEG-4 Part 2
  • ProRes、ProRes Raw
  • AV1 (部分设备)

硬件加速原理

VideoToolbox的硬件加速能力源于其直接访问Apple设备专用硬件编码器/解码器的能力:

  1. 专用硬件模块:Apple芯片包含专用的媒体处理单元,独立于CPU和GPU运作
  2. 低功耗设计:硬件编解码比软件实现减少70-80%的功耗
  3. 零拷贝优化:支持直接在GPU内存和编解码器之间传输数据,减少CPU干预
  4. 并行处理:硬件编码器可与CPU并行工作,提高整体系统性能

视频编码流程详解

编码基本流程

使用VTCompressionSession进行视频编码的核心步骤:

  1. 创建压缩会话:使用VTCompressionSessionCreate函数初始化
  2. 配置会话属性:设置码率、帧率、分辨率等编码参数
  3. 输入视频帧:通过VTCompressionSessionEncodeFrame输入CVPixelBuffer
  4. 处理编码结果:在回调函数中接收编码后的CMSampleBuffer
  5. 结束编码会话:调用VTCompressionSessionCompleteFrames和VTCompressionSessionInvalidate

创建压缩会话

objc 复制代码
static void EncodeCallBack(void *outputCallbackRefCon, void *sourceFrameRefCon, 
                          OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) {
    // 处理编码后的样本缓冲区
    if (status == noErr && sampleBuffer) {
        // 编码成功,处理输出数据
        NSLog(@"编码成功,样本缓冲区大小: %zd", CMSampleBufferGetTotalSampleSize(sampleBuffer));
        // 在这里可以将编码数据写入文件或发送网络
    } else {
        NSLog(@"编码失败,状态码: %d", (int)status);
    }
}

- (void)createCompressionSession {
    int width = 1920;
    int height = 1080;
    CMVideoCodecType codecType = kCMVideoCodecType_H264;
    
    OSStatus status = VTCompressionSessionCreate(
        NULL,                  // 分配器
        width,                 // 宽度
        height,                // 高度
        codecType,             // 编解码器类型
        NULL,                  // 编码器规格
        NULL,                  // 源图像缓冲区属性
        NULL,                  // 压缩数据分配器
        EncodeCallBack,        // 输出回调函数
        (__bridge void *)self, // 回调引用
        &_compressionSession   // 会话输出
    );
    
    if (status != noErr) {
        NSLog(@"创建压缩会话失败,状态码: %d", (int)status);
        return;
    }
    
    // 配置实时编码属性
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
    
    // 准备编码
    VTCompressionSessionPrepareToEncodeFrames(_compressionSession);
}

配置编码参数

VideoToolbox提供丰富的编码参数配置选项,以下是常用属性设置:

objc 复制代码
- (void)configureCompressionProperties {
    // 设置码率控制模式为ABR(平均比特率)
    int averageBitRate = 5000000; // 5Mbps
    CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &averageBitRate);
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);
    CFRelease(bitRateRef);
    
    // 设置帧率
    int frameRate = 30;
    CFNumberRef frameRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &frameRate);
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, frameRateRef);
    CFRelease(frameRateRef);
    
    // 设置关键帧间隔(GOP大小)
    int maxKeyFrameInterval = frameRate * 2; // 2秒一个关键帧
    CFNumberRef keyFrameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &maxKeyFrameInterval);
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, keyFrameIntervalRef);
    CFRelease(keyFrameIntervalRef);
    
    // 设置H.264 Profile Level
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_High_AutoLevel);
    
    // 禁用B帧(减少延迟)
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
}

编码视频帧

将从摄像头或其他源获取的CVPixelBuffer输入到编码会话:

objc 复制代码
- (void)encodePixelBuffer:(CVPixelBufferRef)pixelBuffer presentationTime:(CMTime)presentationTime {
    if (!_compressionSession) {
        NSLog(@"压缩会话未初始化");
        return;
    }
    
    // 设置帧时间戳
    VTEncodeInfoFlags flags = 0;
    OSStatus status = VTCompressionSessionEncodeFrame(
        _compressionSession,
        pixelBuffer,
        presentationTime,
        kCMTimeInvalid, // 持续时间
        NULL,           // 编码选项
        NULL,           // 源帧引用
        &flags          // 编码信息标志
    );
    
    if (status != noErr) {
        NSLog(@"编码帧失败,状态码: %d", (int)status);
        // 处理编码失败,可能需要重新创建会话
    }
}

视频解码流程详解

解码基本流程

使用VTDecompressionSession进行视频解码的核心步骤:

  1. 创建格式描述:从SPS/PPS或编码数据创建CMVideoFormatDescription
  2. 创建解压缩会话:使用VTDecompressionSessionCreate函数初始化
  3. 配置解码参数:设置像素格式、解码模式等
  4. 输入编码数据:通过VTDecompressionSessionDecodeFrame输入编码数据
  5. 处理解码结果:在回调中接收解码后的CVPixelBuffer
  6. 释放解码会话:调用VTDecompressionSessionInvalidate释放资源

创建解压缩会话

objc 复制代码
static void DecodeCallBack(void *decompressionOutputRefCon, void *sourceFrameRefCon, 
                          OSStatus status, VTDecodeInfoFlags infoFlags, 
                          CVImageBufferRef imageBuffer, CMTime presentationTime, 
                          CMTime presentationDuration) {
    // 处理解码后的图像缓冲区
    if (status == noErr && imageBuffer) {
        // 解码成功,处理图像数据
        NSLog(@"解码成功,图像尺寸: %dx%d", 
              CVPixelBufferGetWidth(imageBuffer), 
              CVPixelBufferGetHeight(imageBuffer));
        // 在这里可以将图像显示到屏幕或进行后续处理
    } else {
        NSLog(@"解码失败,状态码: %d", (int)status);
    }
}

- (void)createDecompressionSessionWithFormatDescription:(CMVideoFormatDescriptionRef)formatDescription {
    // 设置输出像素缓冲区属性
    NSDictionary *destinationImageBufferAttributes = @{
        (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
        (id)kCVPixelBufferWidthKey : @(CVPixelBufferGetWidth(imageBuffer)),
        (id)kCVPixelBufferHeightKey : @(CVPixelBufferGetHeight(imageBuffer)),
        (id)kCVPixelBufferIOSurfacePropertiesKey : @{}
    };
    
    OSStatus status = VTDecompressionSessionCreate(
        NULL,                          // 分配器
        formatDescription,             // 视频格式描述
        NULL,                          // 解码器规格
        (__bridge CFDictionaryRef)destinationImageBufferAttributes, // 目标图像属性
        &_decompressionSession         // 会话输出
    );
    
    if (status != noErr) {
        NSLog(@"创建解压缩会话失败,状态码: %d", (int)status);
        return;
    }
    
    // 设置解码回调
    VTDecompressionSessionSetOutputCallback(
        _decompressionSession,
        DecodeCallBack,
        (__bridge void *)self,
        NULL
    );
}

处理H.264码流格式

VideoToolbox仅支持AVCC/HVCC格式的码流,需要将Annex-B格式转换为AVCC格式:

objc 复制代码
- (CMSampleBufferRef)sampleBufferFromH264Data:(NSData *)h264Data 
                                 formatDescription:(CMVideoFormatDescriptionRef *)formatDescription {
    const uint8_t *bytes = [h264Data bytes];
    size_t length = [h264Data length];
    
    // 查找NALU起始码
    NSMutableArray *naluArray = [NSMutableArray array];
    size_t start = 0;
    for (size_t i = 2; i < length; i++) {
        if (bytes[i] == 0x01 && bytes[i-1] == 0x00 && bytes[i-2] == 0x00) {
            size_t naluSize = i - start - 3;
            if (naluSize > 0) {
                [naluArray addObject:[NSData dataWithBytes:bytes+start+3 length:naluSize]];
            }
            start = i + 1;
        }
    }
    
    // 处理SPS和PPS创建格式描述
    if (*formatDescription == NULL) {
        for (NSData *naluData in naluArray) {
            const uint8_t *naluBytes = [naluData bytes];
            uint8_t naluType = naluBytes[0] & 0x1F;
            
            if (naluType == 7) { // SPS
                _spsData = naluData;
            } else if (naluType == 8) { // PPS
                _ppsData = naluData;
                
                if (_spsData && _ppsData) {
                    const uint8_t *sps = [_spsData bytes];
                    const uint8_t *pps = [_ppsData bytes];
                    int spsSize = (int)[_spsData length];
                    int ppsSize = (int)[_ppsData length];
                    
                    // 创建格式描述
                    OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
                        kCFAllocatorDefault,
                        1,
                        &sps, &spsSize,
                        &pps, &ppsSize,
                        4, // NALU长度字段大小
                        formatDescription
                    );
                    
                    if (status != noErr) {
                        NSLog(@"创建格式描述失败,状态码: %d", (int)status);
                    }
                }
            }
        }
    }
    
    // 创建CMBlockBuffer和CMSampleBuffer
    if (*formatDescription) {
        CMBlockBufferRef blockBuffer = NULL;
        OSStatus status = CMBlockBufferCreateWithMemoryBlock(
            kCFAllocatorDefault,
            (void *)bytes,
            length,
            kCFAllocatorNull,
            NULL,
            0,
            length,
            0,
            &blockBuffer
        );
        
        if (status != noErr) {
            NSLog(@"创建BlockBuffer失败,状态码: %d", (int)status);
            return NULL;
        }
        
        CMSampleBufferRef sampleBuffer = NULL;
        const size_t sampleSize = length;
        status = CMSampleBufferCreateReady(
            kCFAllocatorDefault,
            blockBuffer,
            *formatDescription,
            1,
            0,
            NULL,
            1,
            &sampleSize,
            &sampleBuffer
        );
        
        if (status != noErr) {
            NSLog(@"创建SampleBuffer失败,状态码: %d", (int)status);
            CFRelease(blockBuffer);
            return NULL;
        }
        
        return sampleBuffer;
    }
    
    return NULL;
}

解码视频数据

将编码数据输入到解压缩会话进行解码:

objc 复制代码
- (void)decodeH264Data:(NSData *)h264Data {
    CMVideoFormatDescriptionRef formatDescription = NULL;
    CMSampleBufferRef sampleBuffer = [self sampleBufferFromH264Data:h264Data 
                                              formatDescription:&formatDescription];
    
    if (!sampleBuffer) {
        NSLog(@"无法创建SampleBuffer");
        return;
    }
    
    if (!_decompressionSession && formatDescription) {
        [self createDecompressionSessionWithFormatDescription:formatDescription];
    }
    
    if (_decompressionSession) {
        VTDecodeFrameFlags flags = kVTDecodeFrame_EnableAsynchronousDecompression;
        VTDecodeInfoFlags infoFlags = 0;
        
        OSStatus status = VTDecompressionSessionDecodeFrame(
            _decompressionSession,
            sampleBuffer,
            flags,
            NULL, // 源帧引用
            &infoFlags
        );
        
        if (status != noErr) {
            NSLog(@"解码帧失败,状态码: %d", (int)status);
        }
    }
    
    CFRelease(sampleBuffer);
    if (formatDescription) CFRelease(formatDescription);
}

高级应用与性能优化

低延迟编码配置

对于实时视频通信场景,配置低延迟编码模式:

objc 复制代码
- (void)configureLowLatencyEncoding {
    // 创建低延迟编码器规格
    CFDictionaryRef encoderSpecification = @{
        (id)kVTVideoEncoderSpecification_EnableLowLatencyRateControl: @YES
    };
    
    // 使用低延迟规格创建会话
    OSStatus status = VTCompressionSessionCreate(
        NULL,
        _width,
        _height,
        kCMVideoCodecType_H264,
        encoderSpecification, // 使用低延迟规格
        NULL,
        NULL,
        EncodeCallBack,
        (__bridge void *)self,
        &_compressionSession
    );
    
    if (status != noErr) {
        NSLog(@"创建低延迟压缩会话失败,状态码: %d", (int)status);
        return;
    }
    
    // 禁用B帧(低延迟关键配置)
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
    
    // 设置最大帧延迟为1(最小化延迟)
    int maxFrameDelay = 1;
    CFNumberRef maxFrameDelayRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &maxFrameDelay);
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_MaxFrameDelayCount, maxFrameDelayRef);
    CFRelease(maxFrameDelayRef);
    
    // 使用Constrained Baseline Profile提高兼容性
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_ConstrainedBaseline_AutoLevel);
}

性能对比与优化策略

VideoToolbox与其他编码方案的性能对比:

编码方案 速度 质量 CPU占用 功耗 适用场景
VideoToolbox硬件编码 快(5-10x) 中等 低(25-30%) 实时通信、直播
libx264软件编码 高(100%) 高质量视频制作
FFmpeg+VAAPI 中(3-5x) 中高 中(50-60%) 跨平台桌面应用

优化建议

  1. 码率控制

    • 实时场景使用ABR模式,设置合理的最小/最大码率
    • 存储场景可使用CQ模式,通过-q:v参数控制质量
  2. 线程管理

    • 使用专用串行队列处理编解码操作
    • 避免在回调中执行耗时操作
  3. 内存优化

    • 复用CVPixelBuffer对象,减少内存分配
    • 监控内存使用,避免在扩展中使用VTPixelRotationSession
  4. 错误恢复

    • 实现会话重建机制,处理编解码失败
    • 使用长期参考帧(LTR)提高丢包恢复能力

实际应用案例

案例1:实时视频会议应用

使用VideoToolbox实现低延迟视频编码:

objc 复制代码
// 配置低延迟参数
[self configureLowLatencyEncoding];

// 设置目标码率为1-2Mbps(适合视频会议)
int averageBitRate = 1500000; // 1.5Mbps
CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &averageBitRate);
VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);
CFRelease(bitRateRef);

// 设置较小的GOP大小(1秒)
int frameRate = 30;
int maxKeyFrameInterval = frameRate * 1; // 1秒一个关键帧
CFNumberRef keyFrameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &maxKeyFrameInterval);
VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, keyFrameIntervalRef);
CFRelease(keyFrameIntervalRef);

案例2:4K视频录制应用

优化高分辨率视频编码性能:

objc 复制代码
// 配置4K编码参数
int width = 3840;
int height = 2160;
CMVideoCodecType codecType = kCMVideoCodecType_HEVC; // 使用HEVC提高压缩效率

// 创建压缩会话
OSStatus status = VTCompressionSessionCreate(NULL, width, height, codecType, NULL, NULL, NULL, EncodeCallBack, (__bridge void *)self, &_compressionSession);

// 设置高码率(4K建议20-30Mbps)
int averageBitRate = 25000000; // 25Mbps
CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &averageBitRate);
VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);
CFRelease(bitRateRef);

// 启用硬件加速优先模式
CFDictionaryRef encoderSpecification = @{
    (id)kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder: @YES
};

常见问题与解决方案

问题1:编码会话创建失败

可能原因

  • 不支持的编解码器类型
  • 分辨率超出硬件限制
  • 设备不支持特定功能

解决方案

objc 复制代码
- (BOOL)createCompressionSessionWithCodecType:(CMVideoCodecType)codecType {
    // 检查硬件支持情况
    BOOL isSupported = NO;
    if (codecType == kCMVideoCodecType_HEVC) {
        if (@available(iOS 11.0, *)) {
            isSupported = VTIsHardwareDecodeSupported(codecType);
        } else {
            isSupported = NO;
        }
    } else {
        isSupported = VTIsHardwareDecodeSupported(codecType);
    }
    
    if (!isSupported) {
        NSLog(@"当前设备不支持%@硬件编码", codecType == kCMVideoCodecType_HEVC ? @"HEVC" : @"H.264");
        // 降级为支持的编解码器
        codecType = kCMVideoCodecType_H264;
    }
    
    // 检查分辨率限制
    CGSize maxResolution = [self maxSupportedResolutionForCodec:codecType];
    if (_width > maxResolution.width || _height > maxResolution.height) {
        NSLog(@"分辨率超出硬件限制,调整为%@", NSStringFromCGSize(maxResolution));
        _width = maxResolution.width;
        _height = maxResolution.height;
    }
    
    // 创建会话...
    return YES;
}

问题2:编码质量不佳

可能原因

  • 码率设置过低
  • Profile Level设置不当
  • 未启用CABAC熵编码

解决方案

objc 复制代码
// 提高编码质量的配置
- (void)improveEncodingQuality {
    // 提高目标码率
    int averageBitRate = 8000000; // 8Mbps for 1080p
    CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &averageBitRate);
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);
    CFRelease(bitRateRef);
    
    // 使用High Profile
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_High_AutoLevel);
    
    // 启用CABAC熵编码
    if ([self isSupportPropertyWithSession:_compressionSession key:kVTCompressionPropertyKey_H264EntropyMode]) {
        VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_H264EntropyMode, kVTH264EntropyMode_CABAC);
    }
    
    // 设置最大QP值,限制质量下限
    int maxQP = 35; // 数值越小质量越高,范围0-51
    CFNumberRef maxQPRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &maxQP);
    VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_MaxAllowedFrameQP, maxQPRef);
    CFRelease(maxQPRef);
}

问题3:解码画面闪烁或花屏

可能原因

  • NALU格式不正确
  • SPS/PPS参数集缺失或错误
  • 时间戳不连续

解决方案

objc 复制代码
// 确保正确处理SPS/PPS
- (void)handleParameterSets {
    // 在每个IDR帧前发送SPS/PPS
    if (naluType == 5) { // IDR帧
        if (_spsData && _ppsData) {
            [self sendNALU:_spsData]; // 发送SPS
            [self sendNALU:_ppsData]; // 发送PPS
        }
    }
    
    // 验证时间戳连续性
    if (CMTIME_IS_VALID(_lastPresentationTime) && 
        CMTimeCompare(presentationTime, _lastPresentationTime) <= 0) {
        NSLog(@"时间戳不连续,校正时间戳");
        presentationTime = CMTimeAdd(_lastPresentationTime, CMTimeMake(1, 30)); // 假设30fps
    }
    _lastPresentationTime = presentationTime;
}

总结与展望

VideoToolbox框架为Apple平台提供了强大的硬件加速视频编解码能力,是开发高性能视频应用的关键技术。通过直接访问硬件编码器/解码器,VideoToolbox能够在保持低CPU占用和低功耗的同时,提供高效的视频处理能力。

主要优势

  • 卓越的性能:硬件加速比软件编码快5-10倍
  • 低功耗设计:延长移动设备电池寿命
  • 紧密集成:与Core Media和AVFoundation无缝协作
  • 持续演进:支持最新的编解码标准和硬件特性

未来发展方向

  • AV1编解码支持的进一步完善
  • 空间视频编码的增强(Vision Pro生态)
  • AI辅助编码优化
  • 更精细的码率控制和质量优化

对于需要处理视频的iOS/macOS开发者,掌握VideoToolbox框架将为应用带来显著的性能提升和用户体验改善。通过合理配置参数、优化工作流程和妥善处理边缘情况,开发者可以充分发挥Apple设备的硬件潜力,构建出色的视频应用。

参考资料

  1. Apple官方文档 - VideoToolbox
  2. WWDC2021 - 探索低延迟视频编码
  3. H.264/HEVC码流格式详解
  4. VideoToolbox性能优化指南
  5. FFmpeg与VideoToolbox集成
相关推荐
叽哥2 小时前
flutter学习第 8 节:路由与导航
android·flutter·ios
叽哥2 小时前
flutter学习第 7 节:StatefulWidget 与状态管理基础
android·flutter·ios
dlraba8022 小时前
OpenCV 入门实战:从环境配置到图像 / 视频处理
opencv·计算机视觉·音视频
无线图像传输研究探索3 小时前
无人机图传的得力助手:5G 便携式多卡高清视频融合终端的协同应用
5g·音视频·无人机·无线图传·5g单兵图传·单兵图传·无人机图传
2zcode4 小时前
基于Matlab融合深度学习的视频电梯乘客人数检测平台研究
深度学习·matlab·音视频
ShiMetaPi14 小时前
BM1684X平台:Qwen-2-5-VL图像/视频识别应用
人工智能·音视频·边缘计算·bm1684x·shimetapi
melonbo16 小时前
正向矩阵(DCT)变换后还是一个矩阵,怎么减少存储空间
音视频·h.264
Andy_GF17 小时前
纯血鸿蒙HarmonyOS Next 远程测试包分发
前端·ios·harmonyos
归辞...18 小时前
「iOS」————自动释放池底层原理
macos·ios·cocoa