AVAudioFoundation(3):音视频编辑
本文转自:AVAudioFoundation(3):音视频编辑 | www.samirchen.com
本文主要内容来自 AVFoundation Programming Guide。
音视频编辑
上面简单了解了下 AVFoundation
框架后,我们来看看跟音视频编辑相关的接口。
一个 composition 可以简单的认为是一组轨道(tracks)的集合,这些轨道可以是来自不同媒体资源(asset)。AVMutableComposition
提供了接口来插入或者删除轨道,也可以调整这些轨道的顺序。
下面这张图反映了一个新的 composition 是怎么从已有的 asset 中获取对应的 track 并进行拼接形成新的 asset。
在处理音频时,你可以在使用 AVMutableAudioMix
类的接口来做一些自定义的操作,如下图所示。现在,你可以做到指定一个最大音量或设置一个音频轨道的音量渐变。
如下图所示,我们还可以使用 AVMutableVideoComposition
来直接处理 composition 中的视频轨道。处理一个单独的 video composition 时,你可以指定它的渲染尺寸、缩放比例、帧率等参数并输出最终的视频文件。通过一些针对 video composition 的指令(AVMutableVideoCompositionInstruction 等),我们可以修改视频的背景颜色、应用 layer instructions。这些 layer instructions(AVMutableVideoCompositionLayerInstruction 等)可以用来对 composition 中的视频轨道实施图形变换、添加图形渐变、透明度变换、增加透明度渐变。此外,你还能通过设置 video composition 的 animationTool
属性来应用 Core Animation Framework 框架中的动画效果。
如下图所示,你可以使用 AVAssetExportSession
相关的接口来合并你的 composition 中的 audio mix 和 video composition。你只需要初始化一个 AVAssetExportSession
对象,然后将其 audioMix
和 videoComposition
属性分别设置为你的 audio mix 和 video composition 即可。
创建 Composition
上面简单介绍了集中音视频编辑的场景,现在我们来详细介绍具体的接口。从 AVMutableComposition
开始。
当使用 AVMutableComposition
创建自己的 composition 时,最典型的,我们可以使用 AVMutableCompositionTrack
来向 composition 中添加一个或多个 composition tracks,比如下面这个简单的例子便是向一个 composition 中添加一个音频轨道和一个视频轨道:
AVMutableComposition *mutableComposition = [AVMutableComposition composition];
// Create the video composition track.
AVMutableCompositionTrack *mutableCompositionVideoTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// Create the audio composition track.
AVMutableCompositionTrack *mutableCompositionAudioTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
当为 composition 添加一个新的 track 的时候,需要设置其媒体类型(media type)和 track ID,主要的媒体类型包括:音频、视频、字幕、文本等等。
这里需要注意的是,每个 track 都需要一个唯一的 track ID,比较方便的做法是:设置 track ID 为 kCMPersistentTrackID_Invalid 来为对应的 track 获得一个自动生成的唯一 ID。
向 Composition 添加视听数据
要将媒体数据添加到一个 composition track 中需要访问媒体数据所在的 AVAsset
,可以使用 AVMutableCompositionTrack
的接口将具有相同媒体类型的多个 track 添加到同一个 composition track 中。下面的例子便是从两个 AVAsset
中各取出一份 video asset track,再添加到一个新的 composition track 中去:
// You can retrieve AVAssets from a number of places, like the camera roll for example.
AVAsset *videoAsset = <#AVAsset with at least one video track#>;
AVAsset *anotherVideoAsset = <#another AVAsset with at least one video track#>;
// Get the first video track from each asset.
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *anotherVideoAssetTrack = [[anotherVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
// Add them both to the composition.
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,anotherVideoAssetTrack.timeRange.duration) ofTrack:anotherVideoAssetTrack atTime:videoAssetTrack.timeRange.duration error:nil];
检索兼容的 Composition Tracks
如果可能,每一种媒体类型最好只使用一个 composition track,这样能够优化资源的使用。当你连续播放媒体数据时,应该将相同类型的媒体数据放到同一个 composition track 中,你可以通过类似下面的代码来从 composition 中查找是否有与当前的 asset track 兼容的 composition track,然后拿来使用:
AVMutableCompositionTrack *compatibleCompositionTrack = [mutableComposition mutableTrackCompatibleWithTrack:<#the AVAssetTrack you want to insert#>];
if (compatibleCompositionTrack) {
// Implementation continues.
}
需要注意的是,在同一个 composition track 中添加多个视频段时,当视频段之间切换时可能会丢帧,尤其在嵌入式设备上。基于这个问题,应该合理选择一个 composition track 里的视频段数量。
设置音量渐变
只使用一个 AVMutableAudioMix
对象就能够为 composition 中的每一个 audio track 单独做音频处理。
下面代码展示了如果使用 AVMutableAudioMix
给一个 audio track 设置音量渐变给声音增加一个淡出效果。使用 audioMix
类方法获取 AVMutableAudioMix
实例;然后使用 AVMutableAudioMixInputParameters
类的 audioMixInputParametersWithTrack:
接口将 AVMutableAudioMix
实例与 composition 中的某一个 audio track 关联起来;之后便可以通过 AVMutableAudioMix
实例来处理音量了。
AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];
// Create the audio mix input parameters object.
AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mutableCompositionAudioTrack];
// Set the volume ramp to slowly fade the audio out over the duration of the composition.
[mixParameters setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration)];
// Attach the input parameters to the audio mix.
mutableAudioMix.inputParameters = @[mixParameters];
自定义视频处理
处理音频是我们使用 AVMutableAudioMix
,那么处理视频时,我们就使用 AVMutableVideoComposition
,只需要一个 AVMutableVideoComposition
实例就可以为 composition 中所有的 video track 做处理,比如设置渲染尺寸、缩放、播放帧率等等。
下面我们依次来看一些场景。
设置视频背景色
所有的 video composition 也必然对应一组 AVVideoCompositionInstruction
实例,每个 AVVideoCompositionInstruction
中至少包含一条 video composition instruction。我们可以使用 AVMutableVideoCompositionInstruction
来创建我们自己的 video composition instruction,通过这些指令,我们可以修改 composition 的背景颜色、后处理、layer instruction 等等。
下面的实例代码展示了如何创建 video composition instruction 并将一个 composition 的整个时长都设置为红色背景色:
AVMutableVideoCompositionInstruction *mutableVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mutableVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition.duration);
mutableVideoCompositionInstruction.backgroundColor = [[UIColor redColor] CGColor];
设置透明度渐变
我们也可以用 video composition instructions 来应用 video composition layer instructions。AVMutableVideoCompositionLayerInstruction
可以用来设置 video track 的图形变换、图形渐变、透明度、透明度渐变等等。一个 video composition instruction 的 layerInstructions
属性中所存储的 layer instructions 的顺序决定了 tracks 中的视频帧是如何被放置和组合的。
下面的示例代码展示了如何在从一个视频切换到第二个视频时添加一个透明度渐变的效果:
AVAsset *firstVideoAssetTrack = <#AVAssetTrack representing the first video segment played in the composition#>;
AVAsset *secondVideoAssetTrack = <#AVAssetTrack representing the second video segment played in the composition#>;
// Create the first video composition instruction.
AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
// Create the layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Create the opacity ramp to fade out the first video track over its entire duration.
[firstVideoLayerInstruction setOpacityRampFromStartOpacity:1.f toEndOpacity:0.f timeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration)];
// Create the second video composition instruction so that the second video track isn't transparent.
AVMutableVideoCompositionInstruction *secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
// Create the second layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Attach the first layer instruction to the first video composition instruction.
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
// Attach the second layer instruction to the second video composition instruction.
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
// Attach both of the video composition instructions to the video composition.
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];
动画效果
我们还能通过设置 video composition 的 animationTool
属性来使用 Core Animation Framework 框架的强大能力。比如:设置视频水印、视频标题、动画浮层等。
在 video composition 中使用 Core Animation 有两种不同的方式:
- 添加一个 Core Animation Layer 作为独立的 composition track
- 直接使用 Core Animation Layer 在视频帧中渲染动画效果
下面的代码展示了后面一种使用方式,在视频区域的中心添加水印:
CALayer *watermarkLayer = <#CALayer representing your desired watermark image#>;
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
videoLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
[parentLayer addSublayer:videoLayer];
watermarkLayer.position = CGPointMake(mutableVideoComposition.renderSize.width/2, mutableVideoComposition.renderSize.height/4);
[parentLayer addSublayer:watermarkLayer];
mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
一个完整示例
这里的示例将展示如何合并两个 video asset tracks 和一个 audio asset track 到一个视频文件,其中大体步骤如下:
- 创建一个
AVMutableComposition
对象,添加多个AVMutableCompositionTrack
对象 - 在各个 composition tracks 中添加
AVAssetTrack
对应的时间范围 - 检查 video asset track 的
preferredTransform
属性决定视频方向 - 使用
AVMutableVideoCompositionLayerInstruction
对象对视频进行图形变换 - 设置 video composition 的
renderSize
和frameDuration
属性 - 导出视频文件
- 保存视频文件到相册
下面的示例代码省略了一些内存管理和通知移除相关的代码。
// 1、创建 composition。创建一个 composition,并添加一个 audio track 和一个 video track。
AVMutableComposition *mutableComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 2、添加 asset。从源 assets 中取得两个 video track 和一个 audio track,在上面的 video composition track 中依次添加两个 video track,在 audio composition track 中添加一个 video track。
AVAsset *firstVideoAsset = <#First AVAsset with at least one video track#>;
AVAsset *secondVideoAsset = <#Second AVAsset with at least one video track#>;
AVAsset *audioAsset = <#AVAsset with at least one audio track#>;
AVAssetTrack *firstVideoAssetTrack = [[firstVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *secondVideoAssetTrack = [[secondVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration) ofTrack:firstVideoAssetTrack atTime:kCMTimeZero error:nil];
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondVideoAssetTrack.timeRange.duration) ofTrack:secondVideoAssetTrack atTime:firstVideoAssetTrack.timeRange.duration error:nil];
[audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration)) ofTrack:audioAssetTrack atTime:kCMTimeZero error:nil];
// 3、检查 composition 方向。在 composition 中添加了 audio track 和 video track 后,还必须确保其中所有的 video track 的视频方向都是一致的。在默认情况下 video track 默认为横屏模式,如果这时添加进来的 video track 是在竖屏模式下采集的,那么导出的视频会出现方向错误。同理,将一个横向的视频和一个纵向的视频进行合并导出,export session 会报错。
BOOL isFirstVideoPortrait = NO;
CGAffineTransform firstTransform = firstVideoAssetTrack.preferredTransform;
// Check the first video track's preferred transform to determine if it was recorded in portrait mode.
if (firstTransform.a == 0 && firstTransform.d == 0 && (firstTransform.b == 1.0 || firstTransform.b == -1.0) && (firstTransform.c == 1.0 || firstTransform.c == -1.0)) {
isFirstVideoPortrait = YES;
}
BOOL isSecondVideoPortrait = NO;
CGAffineTransform secondTransform = secondVideoAssetTrack.preferredTransform;
// Check the second video track's preferred transform to determine if it was recorded in portrait mode.
if (secondTransform.a == 0 && secondTransform.d == 0 && (secondTransform.b == 1.0 || secondTransform.b == -1.0) && (secondTransform.c == 1.0 || secondTransform.c == -1.0)) {
isSecondVideoPortrait = YES;
}
if ((isFirstVideoAssetPortrait && !isSecondVideoAssetPortrait) || (!isFirstVideoAssetPortrait && isSecondVideoAssetPortrait)) {
UIAlertView *incompatibleVideoOrientationAlert = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"Cannot combine a video shot in portrait mode with a video shot in landscape mode." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
[incompatibleVideoOrientationAlert show];
return;
}
// 4、应用 Video Composition Layer Instructions。一旦你知道你要合并的视频片段的方向是兼容的,那么你接下来就可以为每个片段应用必要的 layer instructions,并将这些 layer instructions 添加到 video composition 中。
// 所有的 `AVAssetTrack` 对象都有一个 `preferredTransform` 属性,包含了 asset track 的方向信息。这个 transform 会在 asset track 在屏幕上展示时被应用。在下面的代码中,layer instruction 的 transform 被设置为 asset track 的 transform,便于在你修改了视频尺寸时,新的 composition 中的视频也能正确的进行展示。
AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set the time range of the first instruction to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
AVMutableVideoCompositionInstruction *secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set the time range of the second instruction to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
// 创建两个 video layer instruction,关联对应的 video composition track,并设置 transform 为 preferredTransform。
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
// Set the transform of the first layer instruction to the preferred transform of the first video track.
[firstVideoLayerInstruction setTransform:firstTransform atTime:kCMTimeZero];
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
// Set the transform of the second layer instruction to the preferred transform of the second video track.
[secondVideoLayerInstruction setTransform:secondTransform atTime:firstVideoAssetTrack.timeRange.duration];
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];
// 5、设置渲染尺寸和帧率。要完全解决视频方向问题,你还需要调整 video composition 的 `renderSize` 属性,同时也需要设置一个合适的 `frameDuration`,比如 1/30 表示 30 帧每秒。此外,`renderScale` 默认值为 1.0。
CGSize naturalSizeFirst, naturalSizeSecond;
// If the first video asset was shot in portrait mode, then so was the second one if we made it here.
if (isFirstVideoAssetPortrait) {
// Invert the width and height for the video tracks to ensure that they display properly.
naturalSizeFirst = CGSizeMake(firstVideoAssetTrack.naturalSize.height, firstVideoAssetTrack.naturalSize.width);
naturalSizeSecond = CGSizeMake(secondVideoAssetTrack.naturalSize.height, secondVideoAssetTrack.naturalSize.width);
} else {
// If the videos weren't shot in portrait mode, we can just use their natural sizes.
naturalSizeFirst = firstVideoAssetTrack.naturalSize;
naturalSizeSecond = secondVideoAssetTrack.naturalSize;
}
float renderWidth, renderHeight;
// Set the renderWidth and renderHeight to the max of the two videos widths and heights.
if (naturalSizeFirst.width > naturalSizeSecond.width) {
renderWidth = naturalSizeFirst.width;
} else {
renderWidth = naturalSizeSecond.width;
}
if (naturalSizeFirst.height > naturalSizeSecond.height) {
renderHeight = naturalSizeFirst.height;
} else {
renderHeight = naturalSizeSecond.height;
}
mutableVideoComposition.renderSize = CGSizeMake(renderWidth, renderHeight);
// Set the frame duration to an appropriate value (i.e. 30 frames per second for video).
mutableVideoComposition.frameDuration = CMTimeMake(1,30);
// 6、导出 composition 并保持到相册。创建一个 `AVAssetExportSession` 对象,设置对应的 `outputURL` 来将视频导出到指定的文件。同时,我们还可以用 `ALAssetsLibrary` 接口来将导出的视频文件存储到相册中去。
// Create a static date formatter so we only have to initialize it once.
static NSDateFormatter *kDateFormatter;
if (!kDateFormatter) {
kDateFormatter = [[NSDateFormatter alloc] init];
kDateFormatter.dateStyle = NSDateFormatterMediumStyle;
kDateFormatter.timeStyle = NSDateFormatterShortStyle;
}
// Create the export session with the composition and set the preset to the highest quality.
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality];
// Set the desired output URL for the file created by the export process.
exporter.outputURL = [[[[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:@YES error:nil] URLByAppendingPathComponent:[kDateFormatter stringFromDate:[NSDate date]]] URLByAppendingPathExtension:CFBridgingRelease(UTTypeCopyPreferredTagWithClass((CFStringRef)AVFileTypeQuickTimeMovie, kUTTagClassFilenameExtension))];
// Set the output file type to be a QuickTime movie.
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.shouldOptimizeForNetworkUse = YES;
exporter.videoComposition = mutableVideoComposition;
// Asynchronously export the composition to a video file and save this file to the camera roll once export completes.
[exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (exporter.status == AVAssetExportSessionStatusCompleted) {
ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
if ([assetsLibrary videoAtPathIsCompatibleWithSavedPhotosAlbum:exporter.outputURL]) {
[assetsLibrary writeVideoAtPathToSavedPhotosAlbum:exporter.outputURL completionBlock:NULL];
}
}
});
}];
其他参考
AVAudioFoundation(3):音视频编辑的更多相关文章
- AVAudioFoundation(4):音视频录制
本文转自:AVAudioFoundation(4):音视频录制 | www.samirchen.com 本文主要内容来自 AVFoundation Programming Guide. 采集设备的音视 ...
- 音视频基本概念和FFmpeg的简单入门
写在前面 最近正好有音视频编辑的需求,虽然之前粗略的了解过FFmpeg不过肯定是不够用的,借此重新学习下: 基本概念 容器/文件(Conainer/File): 即特定格式的多媒体文件,一般来说一个视 ...
- AVAudioFoundation(5):音视频导出
本文转自:AVAudioFoundation(5):音视频导出 | www.samirchen.com 本文主要内容来自 AVFoundation Programming Guide. 要读写音视频数 ...
- FFmpeg Android 学习(一):Android 如何调用 FFMPEG 编辑音视频
一.概述 在Android开发中,我们对一些音视频的处理比较无力,特别是编辑音视频这部分.而且在Android上对视频编辑方面,几乎没有任何API做支持,MediaCodec(硬编码)也没有做支持.那 ...
- FFmpeg命令行工具和批处理脚本进行简单的音视频文件编辑
FFmpeg_Tutorial FFmpeg工具和sdk库的使用demo 一.使用FFmpeg命令行工具和批处理脚本进行简单的音视频文件编辑 1.基本介绍 对于每一个从事音视频技术开发的工程师,想必没 ...
- android音视频点/直播模块开发
音视频 版权声明:本文为博主原创文章,未经博主允许不得转载. 前言 随着音视频领域的火热,在很多领域(教育,游戏,娱乐,体育,跑步,餐饮,音乐等)尝试做音视频直播/点播功能,那么作为开发一个小白, ...
- Android IOS WebRTC 音视频开发总结(五七)-- 网络传输上的一种QoS方案
本文主要介绍一种QoS的解决方案,文章来自博客园RTC.Blacker,欢迎关注微信公众号blacker,更多详见www.rtc.help QoS出现的背景: 而当网络发生拥塞的时候,所有的数据流都有 ...
- Android IOS WebRTC 音视频开发总结(五六)-- 如何测试网络性能?
本文主要介绍如何测试网络性能,文章来自博客园RTC.Blacker,欢迎关注微信公众号blacker,更多详见www.rtc.help 网络性能直接决定了视频通话效果,比如qq,很多时候我们我们觉得通 ...
- Android IOS WebRTC 音视频开发总结(九)-- webrtc入门001
下面这篇介绍webrtc的文章不错,我花了大半天翻译了一下. 翻译的时候不是逐字逐句的,而是按照自己的理解翻译的,同时为了便于理解,也加入一些自己组织的语言. 本文主要介绍webrtc的信令,stun ...
随机推荐
- C++11新特性之九——function、bind以及lamda表达式总结
本文是C++0x系列的第四篇,主要是内容是C++0x中新增的lambda表达式, function对象和bind机制.之所以把这三块放在一起讲,是因为这三块之间有着非常密切的关系,通过对比学习,加深对 ...
- 进程防结束之PS_CROSS_THREAD_FLAGS_SYSTEM
有人投到黑防去了,不过黑防不厚道,竟然没给完整的代码,自己整理一份备用吧,驱网.DebugMan.邪八的那群人直接飘过吧. 这种方法的关键在于给线程的ETHREAD.CrossThreadFlags设 ...
- MQTT协议笔记之mqtt.io项目Websocket协议支持
前言 MQTT协议专注于网络.资源受限环境,建立之初不曾考虑WEB环境,倒也正常.虽然如此,但不代表它不适合HTML5环境. HTML5 Websocket是建立在TCP基础上的双通道通信,和TCP通 ...
- Android技巧分享——Android开发超好用工具吐血推荐(转)
内容中包含 base64string 图片造成字符过多,拒绝显示
- OneThink友情链接插件使用!
OneThink友情链接插件使用: 直接安装插件就好,查看数据库会有:onethink_links 这个表: 写 links 标签,调用友情链接: <?php namespace Common\ ...
- VC编译选项 多线程(/MT)
VC编译选项 多线程(/MT)多线程调试(/MTd)多线程 DLL (/MD)多线程调试 DLL (/MDd)C 运行时库 库文件Single threa ...
- zabbix_server部署,启动,及端口未监听问题
安装 下载地址:wget https://jaist.dl.sourceforge.net/project/zabbix/ZABBIX%20Latest%20Stable/3.2.6/zabbix-3 ...
- 170719、springboot编程之异步调用@Async
1.在pom.xml中增加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artif ...
- 南京网络赛B-The writing on the wall
30.43% 2000ms 262144K Feeling hungry, a cute hamster decides to order some take-away food (like frie ...
- rbac - 界面、权限
一.模板继承 知识点: users.html / roles.html 继承自 base.html 滑动时,固定 position: fixed;top:60px;bottom:0;left:0;wi ...