美文网首页视频相关
iOS :视频嵌入srt字幕方式一

iOS :视频嵌入srt字幕方式一

作者: 豆浆油条cc | 来源:发表于2020-01-02 17:30 被阅读0次

在视频功能里,一般对视频加文字是使用AVMutableVideoComposition加载一层文字图层,但是想要对一个视频嵌入一个srt文件中的所有字幕呢?
难点是一个srt文件中,每一段文字都有一个时间节点,需要解析时间节点把文字嵌入到视频中。
第一种方式:
AVAssetReader+AVAssetWriter
这两个对象都是AVFoundation中的,通过它们可以对视频解码生成CMSampleBufferRef对象,这个对象包含了视频,音频中每一片流中的信息,并且可以转换成UIImage单独对流操作,可以对视频、音频文件重新编码:码率、帧率、分辨率、比特率、声道等。

AVAssetReader读取视频中的appendPixelBuffer,把它转换为UIImage,根据时间节点把字幕嵌入到UIImage中,这样一张图片就有了文字,然后再转换回CVPixelBufferRef,再用AVAssetWriterInputPixelBufferAdaptor的appendPixelBuffer方法写入一个新的视频流文件中,最终生成一个带有字幕的视频。
缺点:耗时久,cpu加载过高,把控好内存泄漏。
还有一个坑点,因为视频中可能会有旋转角度,比方说我使用8P拍的竖屏视频,再使用AVAssetReader解码的时候,解出来的CMSampleBufferRef都会旋转90度,所以需要对AVAssetWriterInput对象中的transform赋值,但是这种方式是在输出的时候对流旋转,在现在的字幕的方案的流程是
CMSampleBufferRef转换UIImage、
UIImage写文字、
UIImage转换CVPixelBufferRef。

所以需要改为:
CMSampleBufferRef转换UIImage,UIImage旋转90度
UIImage写文字,UIImage旋转回去、
UIImage转换CVPixelBufferRef。

效果图


Untitled.gif

AVAssetReader读取类:

+ (instancetype)initReader:(NSString *)videoPath{
    
    ccAssetReaderManager* manager = [[ccAssetReaderManager alloc] init];
    manager.videoAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:videoPath]];
    
    if ([manager.reader canAddOutput:manager.readerTrackOutput_video]) {
        [manager.reader addOutput:manager.readerTrackOutput_video];
    }
    if ([manager.reader canAddOutput:manager.readerTrackOutput_audio]) {
        [manager.reader addOutput:manager.readerTrackOutput_audio];
    }
    [manager.reader startReading];
    return manager;
}

- (CMSampleBufferRef)nextVideoSample{
    if (!_readerTrackOutput_video) {
        return nil;
    }
    return [_readerTrackOutput_video copyNextSampleBuffer];
}
- (CMSampleBufferRef)nextAudioSample{
    if (!_readerTrackOutput_audio) {
        return nil;
    }
    return [_readerTrackOutput_audio copyNextSampleBuffer];
}

- (void)cancel{
    [self.reader cancelReading];
}

-(AVAssetReader *)reader{
    if (!_reader) {
        _reader = [[AVAssetReader alloc] initWithAsset:self.videoAsset error:nil];
    }
    return _reader;
}
-(AVAssetReaderTrackOutput *)readerTrackOutput_video{
    if (!_readerTrackOutput_video) {
        AVAssetTrack *videoTrack = [[self.videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
        _readerTrackOutput_video = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:self.videoSetting];
        _readerTrackOutput_video.alwaysCopiesSampleData = NO;
    }
    return _readerTrackOutput_video;
}
-(AVAssetReaderTrackOutput *)readerTrackOutput_audio{
    if (!_readerTrackOutput_audio) {
        AVAssetTrack *audioTrack = [[self.videoAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
        _readerTrackOutput_audio = [[AVAssetReaderTrackOutput alloc] initWithTrack:audioTrack outputSettings:self.audioSetting];
        _readerTrackOutput_audio.alwaysCopiesSampleData = NO;
    }
    return _readerTrackOutput_audio;
}
-(NSDictionary *)videoSetting{
    if (!_videoSetting) {
        _videoSetting = @{
            (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
        };
    }
    return _videoSetting;
}
-(NSDictionary *)audioSetting{
    if (!_audioSetting) {
        _audioSetting = @{
            AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM]
        };
    }
    return _audioSetting;
}

AVAssetWriter写入类:

+ (instancetype)initWriter:(NSString *)outPutPath inputPath:(NSString*)inputPath{
    
    ccAssetWriterManager* manager = [[ccAssetWriterManager alloc] init];
    manager.videoUrl = [NSURL fileURLWithPath:outPutPath];
    manager.videoUrl_input = [NSURL fileURLWithPath:inputPath];
    
    manager.group = dispatch_group_create();
    manager.queue_video = dispatch_queue_create("queue_video", DISPATCH_QUEUE_CONCURRENT);
    manager.queue_audio = dispatch_queue_create("queue_audio", DISPATCH_QUEUE_CONCURRENT);
    
    AVAsset* asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:inputPath]];
    //需要根据原视频的旋转角度旋转
    manager.assetWriterInput_video.transform = [manager getVideoOrientationWithAsset:asset];
    
    if ([manager.writer canAddInput:manager.assetWriterInput_video]) {
        [manager.writer addInput:manager.assetWriterInput_video];
    }
    if ([manager.writer canAddInput:manager.assetWriterInput_audio]) {
        [manager.writer addInput:manager.assetWriterInput_audio];
    }
    [manager adaptor];
    
    [manager.writer startWriting];
    [manager.writer startSessionAtSourceTime:kCMTimeZero];
    
    return manager;
}

// 开始写入
- (void)pushAudioBuffer:(CMSampleBufferRef(^)(void))getBufferBlock{
    
    dispatch_group_enter(self.group);
    [self.assetWriterInput_audio requestMediaDataWhenReadyOnQueue:self.queue_audio usingBlock:^{
        if ([self startWrite:self.assetWriterInput_audio getBufferBlock:getBufferBlock]) {
            //关闭会话
            [self.assetWriterInput_audio markAsFinished];
            dispatch_group_leave(self.group);
        }
    }];
}
- (void)pushVideoBuff:(CVPixelBufferRef(^)(void))getBufferBlock getBufferTimeBlock:(CMTime(^)(void))getBufferTimeBlock{
    dispatch_group_enter(self.group);
    
    dispatch_async(self.queue_video, ^{
        if ([self startWrite:self.adaptor getBufferBlock:getBufferBlock getBufferTimeBlock:getBufferTimeBlock]) {
            //关闭会话
            [self.assetWriterInput_video markAsFinished];
            dispatch_group_leave(self.group);
        }
    });
}
- (void)finishHandle:(void(^)(bool))handle{
    //队列执行完成
    dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
        [self.writer finishWritingWithCompletionHandler:^{
            AVAssetWriterStatus status = self.writer.status;
            
            dispatch_async(dispatch_get_main_queue(), ^{
                if (handle) {
                    handle(status == AVAssetWriterStatusCompleted);
                }
            });
        }];
    });
}

- (void)cancel{
    [self.writer cancelWriting];
}

- (BOOL)startWrite:(AVAssetWriterInputPixelBufferAdaptor*)writerInput getBufferBlock:(CVPixelBufferRef(^)(void))getBufferBlock getBufferTimeBlock:(CMTime(^)(void))getBufferTimeBlock{

    BOOL complete = NO;
    AVAsset* asset = [AVAsset assetWithURL:self.videoUrl_input];
    
    while (!complete && self.assetWriterInput_video.isReadyForMoreMediaData) {
        //可以写入
        @autoreleasepool {
            CVPixelBufferRef buffer = getBufferBlock();
            CMTime pts = getBufferTimeBlock();
            
            if (CMTIME_COMPARE_INLINE(pts, >, asset.duration)) {
                complete = YES;
                break;
            }
            [self.writer startSessionAtSourceTime:pts];

            NSLog(@"插入图片:%f---%ld",CMTimeGetSeconds(pts),(long)self.writer.status);
            if (buffer) {
                [_adaptor appendPixelBuffer:buffer withPresentationTime:pts];
                CFRelease(buffer);
                buffer = NULL;
            }else{
                complete = YES;
            }
        }
    }
    return complete;
}

- (BOOL)startWrite:(AVAssetWriterInput*)writerInput getBufferBlock:(CMSampleBufferRef(^)(void))getBufferBlock{
    
    BOOL complete = NO;
    while (!complete && writerInput.isReadyForMoreMediaData) {
        //可以写入
        @autoreleasepool {
            CMSampleBufferRef buffer = getBufferBlock();
            if (buffer) {
                [writerInput appendSampleBuffer:buffer];
                CFRelease(buffer);
                buffer = NULL;
            }else{
                complete = YES;
            }
        }
    }
    return complete;
}

//获取视频旋转角度
- (CGSize)getVideoOutPutNaturalSizeWithAsset:(AVAsset*)asset{
    
    NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
    AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
    CGFloat width = videoTrack.naturalSize.width;
    CGFloat height = videoTrack.naturalSize.height;
    
    CGSize size = CGSizeZero;
    CGAffineTransform videoTransform = videoTrack.preferredTransform;//矩阵旋转角度
    
    if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) {
        size = CGSizeMake(width, height);
    }
    if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) {
        size = CGSizeMake(width, height);
    }
    if (videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0) {
        size = CGSizeMake(height, width);
    }
    if (videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0) {
        size = CGSizeMake(height, width);
    }
    
    return size;
}

//获取视频旋转角度
- (CGAffineTransform)getVideoOrientationWithAsset:(AVAsset*)asset{
    
    NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
    AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
    return videoTrack.preferredTransform;
}

-(AVAssetWriter *)writer{
    if (!_writer) {
        _writer = [[AVAssetWriter alloc] initWithURL:self.videoUrl fileType:AVFileTypeMPEG4 error:nil];
    }
    return _writer;
}
-(AVAssetWriterInputPixelBufferAdaptor *)adaptor{
    if (!_adaptor) {
        CGSize size = [self getVideoOutPutNaturalSizeWithAsset:[AVAsset assetWithURL:self.videoUrl_input]];
        NSDictionary *dic = @{
            (id)kCVPixelBufferPixelFormatTypeKey:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA],
            AVVideoCodecKey :  AVVideoCodecTypeH264,
            AVVideoWidthKey :  @(size.width),
            AVVideoHeightKey : @(size.height),
            (id)kCVPixelFormatOpenGLESCompatibility : @(NO)
        };
        _adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self.assetWriterInput_video sourcePixelBufferAttributes:dic];
    }
    return _adaptor;
}
-(AVAssetWriterInput *)assetWriterInput_video{
    if (!_assetWriterInput_video) {
        
        CGSize size = [self getVideoOutPutNaturalSizeWithAsset:[AVAsset assetWithURL:self.videoUrl_input]];
        self.videoSetting = @{
            AVVideoCodecKey :  AVVideoCodecTypeH264,
            AVVideoWidthKey :  @(size.width),
            AVVideoHeightKey : @(size.height)
        };
        _assetWriterInput_video = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:self.videoSetting];
        _assetWriterInput_video.expectsMediaDataInRealTime = YES;
    }
    return _assetWriterInput_video;
}
-(AVAssetWriterInput *)assetWriterInput_audio{
    if (!_assetWriterInput_audio) {
        
        self.audioSetting = @{
            AVFormatIDKey :          @(kAudioFormatMPEG4AAC),
            AVNumberOfChannelsKey :  @(2),
            AVSampleRateKey :        @(44100),
            AVEncoderBitRateKey :    @(64000),
        };
        
        _assetWriterInput_audio = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:self.audioSetting];
    }
    return _assetWriterInput_audio;
}

使用它们嵌入字幕

- (void)initReader:(NSString*)inputPath subtitles:(NSArray*)subtitles outputPath:(NSString*)outputPath handle:(void(^)(bool))handle{
    
    //初始化
    ccAssetReaderManager* manager_reader = [ccAssetReaderManager initReader:inputPath];
    ccAssetWriterManager* manager_writer = [ccAssetWriterManager initWriter:outputPath inputPath:inputPath];

    //读取buffer 写入文件
    __block CMTime pts;
    [manager_writer pushVideoBuff:^CVPixelBufferRef _Nonnull{
        
        CMSampleBufferRef buffer = [manager_reader nextVideoSample];
        __block CVImageBufferRef CVPixelBuffer = CMSampleBufferGetImageBuffer(buffer);

        //时间点
        pts = CMSampleBufferGetPresentationTimeStamp(buffer);
        CGFloat time = CMTimeGetSeconds(pts);

        [subtitles enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            float begin = [obj[@"begin"] floatValue];
            float end = [obj[@"end"] floatValue];
            NSString* subtitle = obj[@"subtitle"];

            if (time>=begin && time<=end) {
                UIImage* image = [self imageWithSampleBuffer:buffer];
                UIImage* image_text = [self addText:subtitle addToView:image];
                UIImage* image_transfrom = [self transfromImage:image_text];
                CVPixelBuffer = [self pixelBufferFromCGImage:image_transfrom.CGImage];
            }
        }];
                
        return CVPixelBuffer;
        
    } getBufferTimeBlock:^CMTime{
        return pts;
    }];
    [manager_writer pushAudioBuffer:^CMSampleBufferRef _Nonnull{
        return [manager_reader nextAudioSample];
    }];
    [manager_writer finishHandle:^(bool success) {
        if (success) {
            [manager_reader cancel];
            [manager_writer cancel];
        }
        handle(success);
    }];
}

//转CMSampleBufferRef-->UIImage 并且旋转
- (UIImage*)imageWithSampleBuffer:(CMSampleBufferRef)buffer {
    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(buffer);
    CIImage *ciimage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
   // 旋转的方法
    CIImage *image = [ciimage imageByApplyingCGOrientation:kCGImagePropertyOrientationRight];
    return [UIImage imageWithCIImage:image];
}
//转UIImage-->UIImage 并且旋转回去
- (UIImage*)transfromImage:(UIImage*)image {
    // 旋转的方法
    CIImage* ciimage = [CIImage imageWithCGImage:image.CGImage];
    ciimage = [ciimage imageByApplyingCGOrientation:kCGImagePropertyOrientationLeft];
//    CIImage *ciimage = [image.CIImage imageByApplyingCGOrientation:kCGImagePropertyOrientationLeft];
    CIContext *context = [CIContext contextWithOptions:nil];
    CGImageRef myImage = [context createCGImage:ciimage fromRect:CGRectMake(0, 0, image.size.height, image.size.width)];
    return [UIImage imageWithCGImage:myImage];
}

//CGImageRef --> CVPixelBufferRef
- (CVPixelBufferRef) pixelBufferFromCGImage: (CGImageRef) image
{
    
    CGSize frameSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image));
    
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:NO], kCVPixelBufferCGImageCompatibilityKey,
                             [NSNumber numberWithBool:NO], kCVPixelBufferCGBitmapContextCompatibilityKey,
                             nil];
    CVPixelBufferRef pxbuffer = NULL;
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, frameSize.width,
                                          frameSize.height,  kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef) options,
                                          &pxbuffer);
    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    // kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst 需要转换成需要的32BGRA空间
    CGContextRef context = CGBitmapContextCreate(pxdata, frameSize.width,
                                                 frameSize.height, 8, 4*frameSize.width, rgbColorSpace,
                                                 kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
    CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image),
                                           CGImageGetHeight(image)), image);
    
    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);
    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
    
    return pxbuffer;
}

// 添加文字水印
- (UIImage*)addText:(NSString*)text addToView:(UIImage*)image{
    
    int w = image.size.width;
    int h = image.size.height;
    
    UIGraphicsBeginImageContext(image.size);
    [image drawInRect:CGRectMake(0, 0, w, h)];
    
    NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
    textStyle.lineBreakMode = NSLineBreakByWordWrapping;
    textStyle.alignment = NSTextAlignmentCenter;//水平居中
    UIFont* font = [UIFont systemFontOfSize:40];
    
    NSDictionary *attr = @{NSFontAttributeName: font, NSForegroundColorAttributeName : [UIColor whiteColor], NSParagraphStyleAttributeName:textStyle,NSKernAttributeName:@(2),NSBackgroundColorAttributeName:[UIColor blackColor]};
        
    [text drawInRect:CGRectMake(0, h - 60, w, 60) withAttributes:attr];
    
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return newImage;
}

附带srt文件解析

// 设置字幕字符串
- (NSArray*)setSrt:(NSString *)srt {
    // 去除\t\r
    NSString *lyric = [NSString stringWithString:srt];
    lyric = [lyric stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    lyric = [lyric stringByReplacingOccurrencesOfString:@"\t" withString:@""];
    NSArray *arr = [lyric componentsSeparatedByString:@"\n"];
    
    NSMutableArray *tempArr = [NSMutableArray new]; // 存放Item的数组
    NSMutableDictionary *itemDic = [NSMutableDictionary dictionary]; // 存放歌词信息的Item
    
    __block NSInteger i = 0; // 标记, 0:序号  1: 时间   2:英文    3:中文
    for (NSString *str in arr) {
        @autoreleasepool {
            NSString *tempStr = [NSString stringWithString:str];
            tempStr = [tempStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
            if (tempStr.length > 0) {
                switch (i) {
                    case 0:
                        [itemDic setObject:tempStr forKey:@"index"];
                        break;
                    case 1:{
                        //时间
                        NSRange range2 = [tempStr rangeOfString:@"-->"];
                        if (range2.location != NSNotFound) {
                            NSString *beginstr = [tempStr substringToIndex:range2.location];
                            beginstr = [beginstr stringByReplacingOccurrencesOfString:@" " withString:@""];
                            NSArray * arr = [beginstr componentsSeparatedByString:@":"];
                            if (arr.count == 3) {
                                NSArray * arr1 = [arr[2] componentsSeparatedByString:@","];
                                if (arr1.count == 2) {
                                    //将开始时间数组中的时间换化成秒为单位的
                                    CGFloat start = [arr[0] floatValue] * 60*60 + [arr[1] floatValue]*60 + [arr1[0] floatValue] + [arr1[1] floatValue]/1000;
                                    [itemDic setObject:@(start) forKey:@"begin"];
                                    
                                    NSString *endstr = [tempStr substringFromIndex:range2.location+range2.length];
                                    endstr = [endstr stringByReplacingOccurrencesOfString:@" " withString:@""];
                                    NSArray * array = [endstr componentsSeparatedByString:@":"];
                                    if (array.count == 3) {
                                        NSArray * arr2 = [array[2] componentsSeparatedByString:@","];
                                        if (arr2.count == 2) {
                                            //将结束时间数组中的时间换化成秒为单位的
                                            CGFloat end = [array[0] floatValue] * 60*60 + [array[1] floatValue]*60 + [arr2[0] floatValue] + [arr2[1] floatValue]/1000;
                                            [itemDic setObject:@(end) forKey:@"end"];
                                        }
                                    }
                                }
                            }
                        }
                        break;
                    }
                    case 2:
                        [itemDic setObject:tempStr forKey:@"subtitle"];
                        break;
                        //                    case 3: {
                        //                        [itemDic setObject:tempStr forKey:@"en"];
                        //                        break;
                        //                    }
                    default:
                        break;
                }
                i ++;
            }else {
                // 遇到空行,就添加到数组
                i = 0;
                NSDictionary *dic = [NSDictionary dictionaryWithDictionary:itemDic];
                [tempArr addObject:dic];
                [itemDic removeAllObjects];
            }
        }
    }
    return tempArr;
}


GitHub链接:
https://github.com/qw9685/srt-.git

另一种方式嵌入字幕,提高性能:https://www.jianshu.com/p/e372a7b98b29

相关文章

网友评论

    本文标题:iOS :视频嵌入srt字幕方式一

    本文链接:https://www.haomeiwen.com/subject/qqpooctx.html