同步推苹果助手电脑版(同步推苹果助手)
Macbook上那些非常好用的几款软件,有没有相见恨晚的感觉?
本内容来源于@什么值得买APP,观点仅代表作者本人 |作者:啥都没有
创作立场声明:乐于分享是中华美德,希望让更多人知道这些棒棒的软件!
Macbook是我日常使用的生产力工具,非常轻薄便于携带而且系统非常稳定
。在长期使用过程中也积累了不少个人觉得好用的软件,在这里给大家分享一下,希望能对大家有所帮助。
1.Lemon Cleaner一般Macbook的硬盘都不会太大,因为大的比较贵。如何清理垃圾以及管理文件就成了另大家头疼的事情。在这里要推荐腾讯出品的柠檬清理。
它具有大文件清理,重复文件清理,相似照片清理,应用卸载,隐私清理,磁盘空间分析,开机启动项管理功能。非常实用而且
完全免费。
2.Bartender随着不断安装新软件,Macbook的右上角菜单栏会非常拥挤,而且到了一定数量窗口切换时会显示不全,使用起来非常不方便,同时也非常不美观。
Bartender的主要用途就是可以定义哪些图标需要在菜单栏显示,对于不需要显示的放入二级菜单,这样就保证了右上角菜单栏的
清爽。
3.Path FinderPath Finder是Macbook上非常好用的文件管理工具,可以比较和同步文件夹,
查看隐藏文件,使用双窗格和全键盘导航来浏览整个文件系统。
文件夹合并,批量重命名,强大的文件过滤是它最厉害的三个功能,非常方便 。
4.滴答清单是否做事没有规划每天
浑浑噩噩不知道在忙啥?是否经常忘记重要事情?是否总是无法集中注意力,总是东瞧瞧西瞧瞧,
无法专注?
滴答清单基于GTD理念,可以帮你进行时间管理,工作规划,每天做什么一目了然。它能轻松记录大小事务并能灵活设置提醒,还支持番茄时钟可以在沉浸的环境中专注做事。
5.karabiner-elements键盘坏了一个键怎么办?Macbook快捷键用不习惯咋办?karabiner-elements 一款开源的macOS键盘修改器,它可以帮你轻松解决这些问题。
它具有简单修改按键功能,比如你想用TAB键代表A修改一下就可以实现。它还只支持组合键修改,最牛的还能自己
配置规则,比如通过规则来配置连续敲击某键几次打开某个软件。
6.有道词典平常难免要查阅一些英文资料或阅读英文PDF,碰到生词容易抓耳挠腮,个人觉得Macbook最好用的词典就是网易有道词典,功能强大而且完全免费。
有道词典支持中英文互译,可以查单词也可以直接翻译句子,同时还具有单词本,可以进行单词复习。
7.有道云笔记知识需要管理,不然看过就容易忘记,用纸笔记
效率低且容易丢失,比如看到一篇好文章总不能抄写下来吧?所以建议用有道云笔记来做自己的
私人知识管理助手。
有道云笔记能高效管理知识,支持文字,图片,语音,手写等录入方式。同时完全兼容MS Office以及PDF。支持微信,微博,网页
一键收藏永不丢失。支持多端同步,非常方便。
8.NTFSToolMacbook默认是无法写入NTFS格式的硬盘或U盘,这样非常不方便,需要命令行或使用商业解决方案,前者比较麻烦后者比较贵,NTFSTool是针对这个问题的免费开源解决方案。
NTFS Tool 是一款绿色的 NTFS 工具,支持 NTFS 磁盘读写、挂载,推出、管理等功能,操作非常简单。
9.结语以上八款软件就是日常Macbook上经常使用的非常棒的软件。相信大家用起来将获益匪浅,同时大家有什么好的日常软件也欢迎在评论区留言进行交流。
iOS完整推流采集音视频数据编码同步合成流
需求
众所周知,原始的音视频数据无法直接在网络上传输,推流需要编码后的音视频数据以合成的视频流,如flv, mov, asf流等,根据接收方需要的格式进行合成并传输,这里以合成asf流为例,讲述一个完整推流过程:即音视频从采集到编码,同步合成asf视频流,然后可以将流进行传输,为了方便,本例将合成的视频流写入一个asf文件中,以供测试.
注意: 测试需要使用终端通过: ffplay播放demo中录制好的文件,因为asf是windows才支持的格式,mac自带播放器无法播放.
1、实现原理采集: 采集视频帧使用CaptureSession,采集音频帧使用Audio Unit编码: 编码视频数据使用VideoToolbox中vtCompresssion硬编,编码音频数据使用audio converter软编.同步: 根据时间戳生成策略合成: 使用FFmpeg mux编码的音视频数据以合成视频流后续: 合成好的视频流可以通过网络传输或是录制成文件2、阅读前提音视频基础知识推荐必读:H264, H265硬件编解码基础及码流分析iOS视频采集实战(CaptureSession)Audio Unit采集音频实战视频编码实战音频编码实战iOS FFmpeg环境搭建代码地址 : iOS完整推流
掘金地址 : iOS完整推流
简书地址 : iOS完整推流
博客地址 : iOS完整推流
3、总体架构1.mux
对于iOS而言,我们可以通过底层API捕获视频帧与音频帧数据,捕获视频帧使用Foundation框架中的CaptureSession, 其实它同时也可以捕获音频数据,而因为我们想使用最低延时与最高音质的音频, 所以需要借助最底层的音频捕捉框架Audio Unit,然后使用VideoToolbox框架中的VTCompressionSessionRef可以对视频数据进行编码,使用AudioConverter可以对音频数据进行编码,我们在采集时可以将第一帧I帧产生时的系统时间作为音视频时间戳的一个起点,往后的视频说都基于此,由此可扩展做音视频同步方案,最终,我们将比那编码好的音视频数据通过FFmpeg进行合成,这里以asf流为例进行合成,并将生成好的asf流写入文件,以供测试. 生成好的asf流可直接用于网络传输.
3.1 简易流程采集视频
创建CaptureSession对象指定分辨率:sessionPreset/activeFormat,指定帧率setActiveVideoMinFrameDuration/setActiveVideoMaxFrameDuration指定摄像头位置:CaptureDevice指定相机其他属性: 曝光,对焦,闪光灯,手电筒等等...将摄像头数据源加入session指定采集视频的格式:yuv,rgb....kCVPixelBufferPixelFormatTypeKey将输出源加入session创建接收视频帧队列:- (void)setSampleBufferDelegate:(nullable id<CaptureVideoDataOutputSampleBufferDelegate>)sampleBufferDelegate queue:(nullable dispatch_queue_t)sampleBufferCallbackQueue将采集视频数据渲染到屏幕:CaptureVideoPreviewLayer在回调函数中获取视频帧数据: CMSampleBufferRef采集音频
配置音频格式ASBD: 采样率,声道数,采样位数,数据精度,每个包中字节数等等...设置采样时间: setPreferredIOBufferDuration创建audio unit对象,指定分类. AudioComponentInstanceNew设置audio unit属性: 打开输入,禁止输出...为接收的音频数据分配大小kAudioUnitProperty_ShouldAllocateBuffer设置接收数据的回调开始audio unit: AudioOutputUnitStart在回调函数中获取音频数据: AudioUnitRender编码视频数据
指定编码器宽高类型回调并创建上下文对象: VTCompressionSessionCreate设置编码器属性:缓存帧数, 帧率, 平均码率, 最大码率, 实时编码, 是否重排序, 配置信息, 编码模式, I帧间隔时间等.准备编码数据: VTCompressionSessionPrepareToEncodeFrames开始编码: VTCompressionSessionEncodeFrame回调函数中获取编码后的数据CMBlockBufferRef根据合成码流格式,这里是asf所以需要Annex B格式,自己组装sps,pps,start code.编码音频数据
提供原始数据类型与编码后数据类型的ASBD指定编码器类型kAudioEncoderComponentType创建编码器AudioConverterNewSpecific设置编码器属性: 比特率, 编码质量等将1024个采样点原始PCM数据传入编码器开始编码: AudioConverterFillComplexBuffer获取编码后的AAC数据音视频同步
以编码的第一帧视频的系统时间作为音视频数据的基准时间戳,随后将采集到音视频数据中的时间戳减去该基准时间戳作为各自的时间戳, 同步有两种策略,一种是以音频时间戳为准, 即当出现错误时,让视频时间戳去追音频时间戳,这样做即会造成看到画面会快进或快退,二是以视频时间戳为准,即当出现错误时,让音频时间戳去追视时间戳,即声音可能会刺耳,不推荐.所以一般使用第一种方案,通过估计下一帧视频时间戳看看如果超出同步范围则进行同步.
FFmpeg合成数据流
初始化FFmpeg相关参数: FormatContext (管理合成上下文), OutputFormat(合成流格式), Stream(音视频数据流)...创建上下文对象FormatContext: avformat_alloc_context根据数据类型生成编码器Codec: avcodec_find_encoder 视频:_CODEC_ID_H264/_CODEC_ID_HEVC,音频:_CODEC_ID_AAC生成流Stream: avformat_new_stream指定音视频流中各个参数信息, 如数据格式,视频宽高帧率,比特率,基准时间,extra data, 音频:采样率,声道数, 采样位数等等.指定上下文及流格式中的音视频编码器id: video_codec_id, audio_codec_id生成视频流头数据: 当音视频编码器都填充到上下文对象后,即可生产该类型对应的头信息, 此头信息作为解码音视频数据的重要信息,一定需要正确合成.avformat_write_header将音视频数据装入动态数组中.合成音视频数据: 通过另一条线程取出动态数组中的音视频数据,通过比较时间戳的方式进行同步合成.将音视频数据装入Packet中产生合成的数据av_write_frameC++音视频学习资料免费获取方法:关注音视频开发T哥,点击「链接」即可免费获取2023年最新C++音视频开发进阶独家免费学习大礼包!
3.2 文件结构2.file
3.3 快速使用初始化相关模块- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. [self configureCamera]; [self configureAudioCapture]; [self configureAudioEncoder]; [self configurevideoEncoder]; [self configureMuxHandler]; [self configureRecorder];}在相机回调中将原始yuv数据送去编码
- (void)xdxCaptureOutput:(CaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(CaptureConnection *)connection { if ([output isKindOfClass:[CaptureVideoDataOutput class]] == YES) { if (selfEncoder) { [selfEncoder startEncodeDataWithBuffer:sampleBuffer isNeedFreeBuffer:NO]; } }}通过回调函数接收编码后的视频数据并将其送给合成流类.
#pragma mark Video Encoder- (void)receiveVideoEncoderData:(XDXVideEncoderDataRef)dataRef { [self.muxHandler addVideoData:dataRef->data size:(int)dataRef->size timestamp:dataRef->timestamp isKeyFrame:dataRef->isKeyFrame isExtraData:dataRef->isExtraData videoFormat:XDXMuxVideoFormatH264];}在采集音频回调中接收音频数据并编码,最终将编码数据也送入合成流类
#pragma mark Audio Capture and Audio Encode- (void)receiveAudioDataByDevice:(XDXCaptureAudioDataRef)audioDataRef { [selfEncoder encodeAudioWithSourceBuffer:audioDataRef->data sourceBufferSize:audioDataRef->size pts:audioDataRef->pts completeHandler:^(XDXAudioEncderDataRef dataRef) { if (dataRef->size > 10) { [self.muxHandler addAudioData:(uint8_t *)dataRef->data size:dataRef->size channelNum:1 sampleRate:44100 timestamp:dataRef->pts]; } free(dataRef->data); }];}先写文件后,随后接收合成后的数据并写入文件.
#pragma mark Mux- (IBAction)startRecordBtnDidClicked:(id)sender { int size = 0; char *data = (char *)[self.muxHandler getStreamHeadWithSize:&size]; [self.recorder startRecordWithIsHead:YES data:data size:size]; self.isRecording = YES;}- (void)receiveStreamWithIsHead:(BOOL)isHead data:(uint8_t *)data size:(int)size { if (isHead) { return; } if (self.isRecording) { [self.recorder startRecordWithIsHead:NO data:(char *)data size:size]; }}4、具体实现
本例中音视频采集编码模块在前面文章中已经详细介绍,这里不再重复,如需帮助请参考上文的阅读前提.下面仅介绍合成流.
4.1 初始化FFmpeg相关对象.FormatContext: 管理合成流上下文对象OutputFormat: 合成流的格式,这里使用的asf数据流Stream: 音视频数据流具体信息- (void)configureFFmpegWithFormat:(const char *)format { if(m_outputContext != NULL) { av_free(m_outputContext); m_outputContext = NULL; } m_outputContext = avformat_alloc_context(); m_outputFormat = av_guess_format(format, NULL, NULL); m_outputContext->oformat = m_outputFormat; m_outputFormat->audio_codec = _CODEC_ID_NONE; m_outputFormat->video_codec = _CODEC_ID_NONE; m_outputContext->nb_streams = 0; m_video_stream = avformat_new_stream(m_outputContext, NULL); m_video_stream->id = 0; m_audio_stream = avformat_new_stream(m_outputContext, NULL); m_audio_stream->id = 1; log4cplus_info(kModuleName, "configure ffmpeg finish.");}4.2 配置视频流的详细信息
设置该编码的视频流中详细的信息, 如编码器类型,配置信息,原始视频数据格式,视频的宽高,比特率,帧率,基准时间戳,extra data等.
这里最重要的就是extra data,注意,因为我们要根据extra data才能生成正确的头数据,而asf流需要的是annux b格式的数据,苹果采集的视频数据格式为avcc所以在编码模块中已经将其转为annux b格式的数据,并通过参数传入,这里可以直接使用,关于这两种格式区别也可以参考阅读前提中的码流介绍的文章.
- (void)configureVideoStreamWithVideoFormat:(XDXMuxVideoFormat)videoFormat extraData:(uint8_t *)extraData extraDataSize:(int)extraDataSize { if (m_outputContext == NULL) { log4cplus_error(kModuleName, "%s: m_outputContext is null",__func__); return; } if(m_outputFormat == NULL){ log4cplus_error(kModuleName, "%s: m_outputFormat is null",__func__); return; } FormatContext *formatContext = avformat_alloc_context(); Stream *stream = NULL; if(XDXMuxVideoFormatH264 == videoFormat) { Codec *codec = avcodec_find_encoder(_CODEC_ID_H264); stream = avformat_new_stream(formatContext, codec); stream->codecpar->codec_id = _CODEC_ID_H264; }else if(XDXMuxVideoFormatH265 == videoFormat) { Codec *codec = avcodec_find_encoder(_CODEC_ID_HEVC); stream = avformat_new_stream(formatContext, codec); stream->codecpar->codec_tag = MKTAG('h', 'e', 'v', 'c'); stream->codecpar->profile = FF_PROFILE_HEVC_MAIN; stream->codecpar->format = _PIX_FMT_YUV420P; stream->codecpar->codec_id = _CODEC_ID_HEVC; } stream->codecpar->format = _PIX_FMT_YUVJ420P; stream->codecpar->codec_type = MEDIA_TYPE_VIDEO; stream->codecpar->width = 1280; stream->codecpar->height = 720; stream->codecpar->bit_rate = 1024*1024; stream->time_base.den = 1000; stream->time_base.num = 1; stream->time_base = (Rational){1, 1000}; stream->codec->flags |= _CODEC_FLAG_GLOBAL_HEADER; memcpy(m_video_stream, stream, sizeof(Stream)); if(extraData) { int newExtraDataSize = extraDataSize + _INPUT_BUFFER_PADDING_SIZE; m_video_stream->codecpar->extradata_size = extraDataSize; m_video_stream->codecpar->extradata = (uint8_t *)av_mallocz(newExtraDataSize); memcpy(m_video_stream->codecpar->extradata, extraData, extraDataSize); } av_free(stream); m_outputContext->video_codec_id = m_video_stream->codecpar->codec_id; m_outputFormat->video_codec = m_video_stream->codecpar->codec_id; self.isReadyForVideo = YES; [self productStreamHead];}4.3 配置音频流的详细信息
首先根据编码音频的类型生成编码器并生成流对象,然后 配置音频流的详细信息,如压缩数据格式,采样率,声道数,比特率,extra data等等.这里要注意的是extra data是为了保存mp4文件时播放器能够正确解码播放准备的,可以参考这几篇文章:audio extra data1,audio extra data2
- (void)configureAudioStreamWithChannelNum:(int)channelNum sampleRate:(int)sampleRate { FormatContext *formatContext = avformat_alloc_context(); Codec *codec = avcodec_find_encoder(_CODEC_ID_AAC); Stream *stream = avformat_new_stream(formatContext, codec); stream->index = 1; stream->id = 1; stream->duration = 0; stream->time_base.num = 1; stream->time_base.den = 1000; stream->start_time = 0; stream->priv_data = NULL; stream->codecpar->codec_type = MEDIA_TYPE_AUDIO; stream->codecpar->codec_id = _CODEC_ID_AAC; stream->codecpar->format = _SAMPLE_FMT_S16; stream->codecpar->sample_rate = sampleRate; stream->codecpar->channels = channelNum; stream->codecpar->bit_rate = 0; stream->codecpar->extradata_size = 2; stream->codecpar->extradata = (uint8_t *)malloc(2); stream->time_base.den = 25; stream->time_base.num = 1; /* * why we put extra data here for audio: when save to MP4 file, the player can not decode it correctly * http://ffmpeg-users.933282.n4.nabble/AAC-decoder-td1013071.html * /d/file/gt/2023-09/ve0zssa5amc.html * extra data have 16 bits: * Audio object type - normally 5 bits, but 11 bits if AOT_ESCAPE * Sampling index - 4 bits * if (Sampling index == 15) * Sample rate - 24 bits * Channel configuration - 4 bits * last reserved- 3 bits * for exmpale: "Low Complexity Sampling frequency 44100Hz, 1 channel mono": * AOT_LC == 2 -> 00010 - * 44.1kHz == 4 -> 0100 + * 44.1kHz == 4 -> 0100 48kHz == 3 -> 0011 * mono == 1 -> 0001 * so extra data: 00010 0100 0001 000 ->0x12 0x8 + 00010 0011 0001 000 ->0x11 0x88 + */ if (stream->codecpar->sample_rate == 44100) { stream->codecpar->extradata[0] = 0x12; //iRig mic HD have two chanel 0x11 if(channelNum == 1) stream->codecpar->extradata[1] = 0x8; else stream->codecpar->extradata[1] = 0x10; }else if (stream->codecpar->sample_rate == 48000) { stream->codecpar->extradata[0] = 0x11; //iRig mic HD have two chanel 0x11 if(channelNum == 1) stream->codecpar->extradata[1] = 0x88; else stream->codecpar->extradata[1] = 0x90; }else if (stream->codecpar->sample_rate == 32000){ stream->codecpar->extradata[0] = 0x12; if (channelNum == 1) stream->codecpar->extradata[1] = 0x88; else stream->codecpar->extradata[1] = 0x90; } else if (stream->codecpar->sample_rate == 16000){ stream->codecpar->extradata[0] = 0x14; if (channelNum == 1) stream->codecpar->extradata[1] = 0x8; else stream->codecpar->extradata[1] = 0x10; }else if(stream->codecpar->sample_rate == 8000){ stream->codecpar->extradata[0] = 0x15; if (channelNum == 1) stream->codecpar->extradata[1] = 0x88; else stream->codecpar->extradata[1] = 0x90; } stream->codec->flags|= _CODEC_FLAG_GLOBAL_HEADER; memcpy(m_audio_stream, stream, sizeof(Stream)); av_free(stream); m_outputContext->audio_codec_id = stream->codecpar->codec_id; m_outputFormat->audio_codec = stream->codecpar->codec_id; self.isReadyForAudio = YES; [self productStreamHead];}4.4 生成流头数据
当前面2,3部都配置完成后,我们将音视频流注入上下文对象及对象中的流格式中,即可开始生成头数据.avformat_write_header
- (void)productStreamHead { log4cplus_debug("record", "%s,line:%d",__func__,__LINE__); if (m_outputFormat->video_codec == _CODEC_ID_NONE) { log4cplus_error(kModuleName, "%s: video codec is NULL.",__func__); return; } if(m_outputFormat->audio_codec == _CODEC_ID_NONE) { log4cplus_error(kModuleName, "%s: audio codec is NULL.",__func__); return; } /* prepare header and save header data in a stream */ if (avio_open_dyn_buf(&m_outputContext->pb) < 0) { avio_close_dyn_buf(m_outputContext->pb, NULL); log4cplus_error(kModuleName, "%s: Format_HTTP_FF_OPEN_DYURL_ERROR.",__func__); return; } /* * HACK to avoid mpeg ps muxer to spit many underflow errors * Default value from FFmpeg * Try to set it use configuration option */ m_outputContext->max_delay = (int)(0.7*_TIME_BASE); int result = avformat_write_header(m_outputContext,NULL); if (result < 0) { log4cplus_error(kModuleName, "%s: Error writing output header, res:%d",__func__,result); return; } uint8_t * output = NULL; int len = avio_close_dyn_buf(m_outputContext->pb, (uint8_t **)(&output)); if(len > 0 && output != NULL) { av_free(output); self.isReadyForHead = YES; if (m_avhead_data) { free(m_avhead_data); } m_avhead_data_size = len; m_avhead_data = (uint8_t *)malloc(len); memcpy(m_avhead_data, output, len); if ([self.delegate respondsToSelector:@selector(receiveStreamWithIsHead:data:size:)]) { [self.delegate receiveStreamWithIsHead:YES data:output size:len]; } log4cplus_error(kModuleName, "%s: create head length = %d",__func__, len); }else{ self.isReadyForHead = NO; log4cplus_error(kModuleName, "%s: product stream header failed.",__func__); }}4.5 然后将传来的音视频数据装入数组中
该数组通过封装C++中的vector实现一个轻量级数据结构以缓存数据.
4.6 合成音视频数据新建一条线程专门合成音视频数据,合成策略即取出音视频数据中时间戳较小的一帧先写,因为音视频数据总体偏差不大,所以理想情况应该是取一帧视频,一帧音频,当然因为音频采样较快,可能会相对多一两帧,而当音视频数据由于某种原因不同步时,则会等待,直至时间戳重新同步才能继续进行合成.
int err = pthread_create(&m_muxThread,NULL,MuxPacket,(__bridge_retained void *)self); if(err != 0){ log4cplus_error(kModuleName, "%s: create thread failed: %s",__func__, strerror(err)); } void * MuxPacket(void *arg) { pthread_setname_np("XDX_MUX_THREAD"); XDXStreamMuxHandler *instance = (__bridge_transfer XDXStreamMuxHandler *)arg; if(instance != nil) { [instance dispatchData]; } return NULL;}#pragma mark Mux- (void)dispatchData { XDXMuxMediaList audioPack; XDXMuxMediaList videoPack; memset(&audioPack, 0, sizeof(XDXMuxMediaList)); memset(&videoPack, 0, sizeof(XDXMuxMediaList)); [m_AudioListPack reset]; [m_VideoListPack reset]; while (true) { int videoCount = [m_VideoListPack count]; int audioCount = [m_AudioListPack count]; if(videoCount == 0 || audioCount == 0) { usleep(5*1000); log4cplus_debug(kModuleName, "%s: Mux dispatch list: v:%d, a:%d",__func__,videoCount, audioCount); continue; } if(audioPack.timeStamp == 0) { [m_AudioListPack popData:&audioPack]; } if(videoPack.timeStamp == 0) { [m_VideoListPack popData:&videoPack]; } if(audioPack.timeStamp >= videoPack.timeStamp) { log4cplus_debug(kModuleName, "%s: Mux dispatch input video time stamp = %llu",__func__,videoPack.timeStamp); if(videoPack.data != NULL && videoPack.data->data != NULL){ [self addVideoPacket:videoPack.data timestamp:videoPack.timeStamp extraDataHasChanged:videoPack.extraDataHasChanged]; av_free(videoPack.data->data); av_free(videoPack.data); }else{ log4cplus_error(kModuleName, "%s: Mux Video Packet data abnormal",__func__); } videoPack.timeStamp = 0; }else { log4cplus_debug(kModuleName, "%s: Mux dispatch input audio time stamp = %llu",__func__,audioPack.timeStamp); if(audioPack.data != NULL && audioPack.data->data != NULL) { [self addAudioPacket:audioPack.data timestamp:audioPack.timeStamp]; av_free(audioPack.data->data); av_free(audioPack.data); }else { log4cplus_error(kModuleName, "%s: Mux audio Packet data abnormal",__func__); } audioPack.timeStamp = 0; } }}4.7 获取合成好的视频流
通过av_write_frame即可获取合成好的数据.
- (void)productDataPacket:(Packet *)packet extraDataHasChanged:(BOOL)extraDataHasChanged { BOOL isVideoIFrame = NO; uint8_t *output = NULL; int len = 0; if (avio_open_dyn_buf(&m_outputContext->pb) < 0) { return; } if(packet->stream_index == 0 && packet->flags != 0) { isVideoIFrame = YES; } if (av_write_frame(m_outputContext, packet) < 0) { avio_close_dyn_buf(m_outputContext->pb, (uint8_t **)(&output)); if(output != NULL) free(output); log4cplus_error(kModuleName, "%s: Error writing output data",__func__); return; } len = avio_close_dyn_buf(m_outputContext->pb, (uint8_t **)(&output)); if(len == 0 || output == NULL) { log4cplus_debug(kModuleName, "%s: mux len:%d or data abnormal",__func__,len); if(output != NULL) av_free(output); return; } if ([self.delegate respondsToSelector:@selector(receiveStreamWithIsHead:data:size:)]) { [self.delegate receiveStreamWithIsHead:NO data:output size:len]; } if(output != NULL) av_free(output);}
原文链接:iOS瀹屾暣鎺ㄦ祦閲囬泦闊宠棰戞暟鎹紪鐮佸悓姝ュ悎鎴愭祦 - 绠€涔�
声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送至邮件举报,一经查实,本站将立刻删除。转载务必注明出处:http://www.hixs.net/article/20231206/169625518087788.html