这个问题之前已经被问过很多次了,但没有任何帮助。我正在使用 AVMutableComposition 合并多个视频。合并视频后,我在 30% - 40% 的视频中出现空白帧。其他人合并得很好。我只是使用 AVPlayer 作为 AVPlayerItem 直接播放合成。代码如下:
AVMutableComposition *mutableComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo
preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
NSMutableArray *instructions = [NSMutableArray new];
CGSize size = CGSizeZero;
CMTime time = kCMTimeZero;
for (AVURLAsset *asset in assets)
{
AVAssetTrack *assetTrack;
assetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *audioAssetTrack = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;
NSError *error;
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, assetTrack.timeRange.duration )
ofTrack:assetTrack
atTime:time
error:&error];
if (error) {
NSLog(@"asset url :: %@",assetTrack.asset);
NSLog(@"Error - %@", error.debugDescription);
}
[audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, assetTrack.timeRange.duration)
ofTrack:audioAssetTrack
atTime:time
error:&error];
if (error) {
NSLog(@"Error - %@", error.debugDescription);
}
AVMutableVideoCompositionInstruction *videoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
videoCompositionInstruction.timeRange = CMTimeRangeMake(time, assetTrack.timeRange.duration);
videoCompositionInstruction.layerInstructions = @[[AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack]];
[instructions addObject:videoCompositionInstruction];
time = CMTimeAdd(time, assetTrack.timeRange.duration);
if (CGSizeEqualToSize(size, CGSizeZero)) {
size = assetTrack.naturalSize;;
}
}
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = instructions;
mutableVideoComposition.frameDuration = CMTimeMake(1, 30);
mutableVideoComposition.renderSize = size;
playerItem = [AVPlayerItem playerItemWithAsset:mutableComposition];
playerItem.videoComposition = mutableVideoComposition;
最佳答案
据我所知,AVMutableVideoCompositionLayerInstruction 不能像您的代码方式那样简单地“附加”或“添加”。
从你的代码来看,我猜你想在合并视频 Assets 时保留视频指令信息,但指令不能直接“复制”。
如果你想这样做,请参阅 AVVideoCompositionLayerInstruction 的文档,例如
getTransformRampForTime:startTransform:endTransform:timeRange:
setTransformRampFromStartTransform:toEndTransform:timeRange:
setTransform:atTime:
getOpacityRampForTime:startOpacity:endOpacity:timeRange:
setOpacityRampFromStartOpacity:toEndOpacity:timeRange:
setOpacity:atTime:
getCropRectangleRampForTime:startCropRectangle:endCropRectangle:timeRange:
setCropRectangleRampFromStartCropRectangle:toEndCropRectangle:timeRange:
setCropRectangle:atTime:
您应该在源轨道上使用 getFoo... 方法,然后为最终轨道计算 insertTime 或 timeRange,然后 setFoo...,然后附加到最终 videoComposition 的 layerInstructions。
是的,有点复杂...此外,最重要的是,您无法获得适用于源 Assets 的所有视频效果。
那么你的目的是什么?你的源 Assets 是用什么支持的?
如果你只是想合并一些 mp4/mov 文件,只需循环轨道并将它们附加到 AVMutableCompositionTrack,而不是 videoComposition。我测试了您的代码,它有效。
如果要合并带有视频说明的 AVAssets,请参见上面的说明和 docs .我的最佳做法是,
在合并之前,使用 AVAssetExportSession 将这些 AVAssets 保存到文件中,然后合并视频文件。
附注也许您的测试文件或源 Assets 存在一些问题。
我的项目代码,例如 Vine:
- (BOOL)generateComposition
{
[self cleanComposition];
NSUInteger segmentsCount = self.segmentsCount;
if (0 == segmentsCount) {
return NO;
}
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableVideoComposition *videoComposition = nil;
AVMutableVideoCompositionInstruction *videoCompositionInstruction = nil;
AVMutableVideoCompositionLayerInstruction *videoCompositionLayerInstruction = nil;
AVMutableAudioMix *audioMix = nil;
AVMutableCompositionTrack *videoTrack = nil;
AVMutableCompositionTrack *audioTrack = nil;
AVMutableCompositionTrack *musicTrack = nil;
CMTime currentTime = kCMTimeZero;
for (MVRecorderSegment *segment in self.segments) {
AVURLAsset *asset = segment.asset;
NSArray *videoAssetTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
NSArray *audioAssetTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
CMTime maxBounds = kCMTimeInvalid;
CMTime videoTime = currentTime;
for (AVAssetTrack *videoAssetTrack in videoAssetTracks) {
if (!videoTrack) {
videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
videoTrack.preferredTransform = CGAffineTransformIdentity;
videoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
videoCompositionLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
}
/* Fix orientation */
CGAffineTransform transform = videoAssetTrack.preferredTransform;
if (AVCaptureDevicePositionFront == segment.cameraPosition) {
transform = CGAffineTransformMakeTranslation(self.config.videoSize, 0);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
} else if (AVCaptureDevicePositionBack == segment.cameraPosition) {
}
[videoCompositionLayerInstruction setTransform:transform atTime:videoTime];
/* Append track */
videoTime = [MVHelper appendAssetTrack:videoAssetTrack toCompositionTrack:videoTrack atTime:videoTime withBounds:maxBounds];
maxBounds = videoTime;
}
if (self.sessionConfiguration.originalVoiceOn) {
CMTime audioTime = currentTime;
for (AVAssetTrack *audioAssetTrack in audioAssetTracks) {
if (!audioTrack) {
audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
}
audioTime = [MVHelper appendAssetTrack:audioAssetTrack toCompositionTrack:audioTrack atTime:audioTime withBounds:maxBounds];
}
}
currentTime = composition.duration;
}
if (videoCompositionInstruction && videoCompositionLayerInstruction) {
videoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, composition.duration);
videoCompositionInstruction.layerInstructions = @[videoCompositionLayerInstruction];
videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.renderSize = CGSizeMake(self.config.videoSize, self.config.videoSize);
videoComposition.frameDuration = CMTimeMake(1, self.config.videoFrameRate);
videoComposition.instructions = @[videoCompositionInstruction];
}
// 添加背景音乐 musicTrack
NSURL *musicFileURL = self.sessionConfiguration.musicFileURL;
if (musicFileURL && musicFileURL.isFileExists) {
AVAsset *musicAsset = [AVAsset assetWithURL:musicFileURL];
AVAssetTrack *musicAssetTrack = [musicAsset tracksWithMediaType:AVMediaTypeAudio].firstObject;
if (musicAssetTrack) {
musicTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
if (CMTIME_COMPARE_INLINE(musicAsset.duration, >=, composition.duration)) {
// 如果背景音乐时长大于视频总时长, 则直接添加
[musicTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, composition.duration) ofTrack:musicAssetTrack atTime:kCMTimeZero error:NULL];
} else {
// 否则, 循环背景音乐
CMTime musicTime = kCMTimeZero;
CMTime bounds = composition.duration;
while (true) {
musicTime = [MVHelper appendAssetTrack:musicAssetTrack toCompositionTrack:musicTrack atTime:musicTime withBounds:bounds];
if (CMTIME_COMPARE_INLINE(musicTime, >=, composition.duration)) {
break;
}
}
}
}
}
// 处理音频
if (musicTrack) {
AVMutableAudioMixInputParameters *audioMixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:musicTrack];
/* 背景音乐添加淡入淡出 */
AVAsset *musicAsset = musicTrack.asset;
CMTime crossfadeDuration = CMTimeMake(15, 10); // 前后都是1.5秒
CMTime halfDuration = CMTimeMultiplyByFloat64(musicAsset.duration, 0.5);
crossfadeDuration = CMTimeMinimum(crossfadeDuration, halfDuration);
CMTimeRange crossfadeRangeBegin = CMTimeRangeMake(kCMTimeZero, crossfadeDuration);
CMTimeRange crossfadeRangeEnd = CMTimeRangeMake(CMTimeSubtract(musicAsset.duration, crossfadeDuration), crossfadeDuration);
[audioMixParameters setVolumeRampFromStartVolume:0.0 toEndVolume:self.sessionConfiguration.musicVolume timeRange:crossfadeRangeBegin];
[audioMixParameters setVolumeRampFromStartVolume:self.sessionConfiguration.musicVolume toEndVolume:0.0 timeRange:crossfadeRangeEnd];
audioMix = [AVMutableAudioMix audioMix];
[audioMix setInputParameters:@[audioMixParameters]];
}
_composition = composition;
_videoComposition = videoComposition;
_audioMix = audioMix;
return YES;
}
- (AVPlayerItem *)playerItem
{
AVPlayerItem *playerItem = nil;
if (self.composition) {
playerItem = [AVPlayerItem playerItemWithAsset:self.composition];
if (!self.videoComposition.animationTool) {
playerItem.videoComposition = self.videoComposition;
}
playerItem.audioMix = self.audioMix;
}
return playerItem;
}
///=============================================
/// MVHelper
///=============================================
+ (CMTime)appendAssetTrack:(AVAssetTrack *)track toCompositionTrack:(AVMutableCompositionTrack *)compositionTrack atTime:(CMTime)atTime withBounds:(CMTime)bounds
{
CMTimeRange timeRange = track.timeRange;
atTime = CMTimeAdd(atTime, timeRange.start);
if (!track || !compositionTrack) {
return atTime;
}
if (CMTIME_IS_VALID(bounds)) {
CMTime currentBounds = CMTimeAdd(atTime, timeRange.duration);
if (CMTIME_COMPARE_INLINE(currentBounds, >, bounds)) {
timeRange = CMTimeRangeMake(timeRange.start, CMTimeSubtract(timeRange.duration, CMTimeSubtract(currentBounds, bounds)));
}
}
if (CMTIME_COMPARE_INLINE(timeRange.duration, >, kCMTimeZero)) {
NSError *error = nil;
[compositionTrack insertTimeRange:timeRange ofTrack:track atTime:atTime error:&error];
if (error) {
MVLog(@"Failed to append %@ track: %@", compositionTrack.mediaType, error);
}
return CMTimeAdd(atTime, timeRange.duration);
}
return atTime;
}
关于ios - 使用 AVMutableComposition 合并视频时出现空白帧,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30371680/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h