водяной знак iPhone на записанном видео.


В моем приложении мне нужно захватить видео и поставить водяной знак на это видео. Водяным знаком должен быть текст(время и примечания). Я видел код, использующий фрейм" QTKit". Однако я читал, что фреймворк недоступен для iPhone.

Заранее благодарю.

5 16

5 ответов:

Используйте AVFoundation. Я бы предложил захватывать кадры с помощью AVCaptureVideoDataOutput, затем накладывать захваченный кадр на изображение водяного знака и, наконец, записывать захваченные и обработанные кадры в файл пользователя AVAssetWriter.

Поиск вокруг переполнения стека, есть тонна фантастических примеров, подробно описывающих, как сделать каждую из этих вещей, которые я упомянул. Я не видел ни одного, который давал бы примеры кода именно для того эффекта, который вы хотели бы, но вы должны быть в состоянии смешивать и сопоставлять довольно легко.

Правка:

Взгляните на эти ссылки:

IPhone: AVCaptureSession capture output crashing (AVCaptureVideoDataOutput) - этот пост может быть полезен просто по характеру содержания соответствующего кода.

AVCaptureDataOutput вернет изображения в виде CMSampleBufferRefs. Преобразуйте их в CGImageRefs, используя следующий код:

    - (CGImageRef) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer // Create a CGImageRef from sample buffer data
{

    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    CVPixelBufferLockBaseAddress(imageBuffer,0);        // Lock the image buffer 

    uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);   // Get information of the image 
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
    size_t width = CVPixelBufferGetWidth(imageBuffer); 
    size_t height = CVPixelBufferGetHeight(imageBuffer); 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 

    CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); 
    CGImageRef newImage = CGBitmapContextCreateImage(newContext); 
    CGContextRelease(newContext); 

    CGColorSpaceRelease(colorSpace); 
    CVPixelBufferUnlockBaseAddress(imageBuffer,0); 
    /* CVBufferRelease(imageBuffer); */  // do not call this!

    return newImage;
}

Оттуда вы бы преобразовались в UIImage,

  UIImage *img = [UIImage imageWithCGImage:yourCGImage];  

Затем используйте

[img drawInRect:CGRectMake(x,y,height,width)]; 

Чтобы нарисовать рамку в контексте, нарисуйте PNG нанесите на него водяной знак, а затем добавьте обработанные изображения к выходному видео с помощью AVAssetWriter. Я бы предложил добавлять их в реальном времени, чтобы вы не заполняли память тоннами изображений UI.

Как экспортировать массив UIImage в виде фильма? - в этом посте показано, как добавить обработанные UI-изображения в видео заданной длительности.

Это должно помочь вам на вашем пути к водяным знакам ваших видео. Не забывайте практиковать хорошее управление памятью, потому что утечка изображений, которые приходят в 20-30fps это отличный способ, чтобы разбить приложение.

Добавление водяного знака гораздо проще. Вам просто нужно использовать CALayer и AVVideoCompositionCoreAnimationTool. Код можно просто скопировать и собрать в том же порядке. Я только что попытался вставить некоторые комментарии между ними для лучшего понимания.

Предположим, что вы уже записали видео, поэтому сначала мы создадим AVURLAsset:

AVURLAsset* videoAsset = [[AVURLAsset alloc]initWithURL:outputFileURL options:nil];
AVMutableComposition* mixComposition = [AVMutableComposition composition];

AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo  preferredTrackID:kCMPersistentTrackID_Invalid];
AVAssetTrack *clipVideoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
[compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) 
                               ofTrack:clipVideoTrack
                                atTime:kCMTimeZero error:nil];

[compositionVideoTrack setPreferredTransform:[[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] preferredTransform]]; 

С помощью этого кода Вы сможете экспортировать видео, но Сначала мы хотим добавить слой с водяным знаком. Обратите внимание, что некоторые коды могут показаться избыточными, но они необходимы для того, чтобы все работало.

Сначала создадим слой с изображением водяного знака:

UIImage *myImage = [UIImage imageNamed:@"icon.png"];
CALayer *aLayer = [CALayer layer];
aLayer.contents = (id)myImage.CGImage;
aLayer.frame = CGRectMake(5, 25, 57, 57); //Needed for proper display. We are using the app icon (57x57). If you use 0,0 you will not see it
aLayer.opacity = 0.65; //Feel free to alter the alpha here

Если нам не нужно изображение, а нужен текст:

CATextLayer *titleLayer = [CATextLayer layer];
titleLayer.string = @"Text goes here";
titleLayer.font = @"Helvetica";
titleLayer.fontSize = videoSize.height / 6;
//?? titleLayer.shadowOpacity = 0.5;
titleLayer.alignmentMode = kCAAlignmentCenter;
titleLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height / 6); //You may need to adjust this for proper display

Следующий код сортирует слой в правильном порядке:

CGSize videoSize = [videoAsset naturalSize]; 
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];   
parentLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
videoLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
[parentLayer addSublayer:videoLayer];
[parentLayer addSublayer:aLayer];
[parentLayer addSublayer:titleLayer]; //ONLY IF WE ADDED TEXT

Теперь мы создаем композицию и добавляем инструкции для вставки слоя:

AVMutableVideoComposition* videoComp = [[AVMutableVideoComposition videoComposition] retain];
videoComp.renderSize = videoSize;
videoComp.frameDuration = CMTimeMake(1, 30);
videoComp.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

/// instruction
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [mixComposition duration]);
AVAssetTrack *videoTrack = [[mixComposition tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVMutableVideoCompositionLayerInstruction* layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
instruction.layerInstructions = [NSArray arrayWithObject:layerInstruction];
videoComp.instructions = [NSArray arrayWithObject: instruction];

И теперь мы готовы экспортировать:

_assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetMediumQuality];//AVAssetExportPresetPassthrough   
_assetExport.videoComposition = videoComp;

NSString* videoName = @"mynewwatermarkedvideo.mov";

NSString *exportPath = [NSTemporaryDirectory() stringByAppendingPathComponent:videoName];
NSURL    *exportUrl = [NSURL fileURLWithPath:exportPath];

if ([[NSFileManager defaultManager] fileExistsAtPath:exportPath]) 
{
    [[NSFileManager defaultManager] removeItemAtPath:exportPath error:nil];
}

_assetExport.outputFileType = AVFileTypeQuickTimeMovie; 
_assetExport.outputURL = exportUrl;
_assetExport.shouldOptimizeForNetworkUse = YES;

[strRecordedFilename setString: exportPath];

[_assetExport exportAsynchronouslyWithCompletionHandler:
 ^(void ) {
     [_assetExport release];
     //YOUR FINALIZATION CODE HERE
 }       
 ];   

[audioAsset release];
[videoAsset release];

Уже ответ, данный @Julio, прекрасно работает в случае objective-c Вот та же кодовая база для Swift 3.0:

Водяной знак и создание квадратного или обрезанного видео, как Instagram

Получение выходного файла из каталога Documents и создание AVURLAsset

    //output file
    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
    let outputPath = documentsURL?.appendingPathComponent("squareVideo.mov")
    if FileManager.default.fileExists(atPath: (outputPath?.path)!) {
        do {
           try FileManager.default.removeItem(atPath: (outputPath?.path)!)
        }
        catch {
            print ("Error deleting file")
        }
    }



    //input file
    let asset = AVAsset.init(url: filePath)
    print (asset)
    let composition = AVMutableComposition.init()
    composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid)

    //input clip
    let clipVideoTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0]

Создайте слой с изображением водяного знака:

    //adding the image layer
    let imglogo = UIImage(named: "video_button")
    let watermarkLayer = CALayer()
    watermarkLayer.contents = imglogo?.cgImage
    watermarkLayer.frame = CGRect(x: 5, y: 25 ,width: 57, height: 57)
    watermarkLayer.opacity = 0.85

Создайте слой с текстом в качестве водяного знака вместо изображения:

    let textLayer = CATextLayer()
    textLayer.string = "Nodat"
    textLayer.foregroundColor = UIColor.red.cgColor
    textLayer.font = UIFont.systemFont(ofSize: 50)
    textLayer.alignmentMode = kCAAlignmentCenter
    textLayer.bounds = CGRect(x: 5, y: 25, width: 100, height: 20)

Тот самый следующий код сортирует слой в правильном порядке:

  let videoSize = clipVideoTrack.naturalSize
    let parentlayer = CALayer()
    let videoLayer = CALayer()

    parentlayer.frame = CGRect(x: 0, y: 0, width: videoSize.height, height: videoSize.height)
    videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.height, height: videoSize.height)
    parentlayer.addSublayer(videoLayer)
    parentlayer.addSublayer(watermarkLayer)
    parentlayer.addSublayer(textLayer) //for text layer only

Добавление слоев поверх видео в правильном порядке для водяного знака

  let videoSize = clipVideoTrack.naturalSize
    let parentlayer = CALayer()
    let videoLayer = CALayer()

    parentlayer.frame = CGRect(x: 0, y: 0, width: videoSize.height, height: videoSize.height)
    videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.height, height: videoSize.height)
    parentlayer.addSublayer(videoLayer)
    parentlayer.addSublayer(watermarkLayer)
    parentlayer.addSublayer(textLayer) //for text layer only

Обрезка видео в квадратном формате-размером 300*300

 //make it square
    let videoComposition = AVMutableVideoComposition()
    videoComposition.renderSize = CGSize(width: 300, height: 300) //change it as per your needs.
    videoComposition.frameDuration = CMTimeMake(1, 30)
    videoComposition.renderScale = 1.0

    //Magic line for adding watermark to the video
    videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayers: [videoLayer], in: parentlayer)

    let instruction = AVMutableVideoCompositionInstruction()
    instruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(60, 30))

Поворот к портрету

//rotate to potrait
    let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: clipVideoTrack)
    let t1 = CGAffineTransform(translationX: clipVideoTrack.naturalSize.height, y: -(clipVideoTrack.naturalSize.width - clipVideoTrack.naturalSize.height) / 2)
    let t2: CGAffineTransform = t1.rotated(by: .pi/2)
    let finalTransform: CGAffineTransform = t2
    transformer.setTransform(finalTransform, at: kCMTimeZero)
    instruction.layerInstructions = [transformer]
    videoComposition.instructions = [instruction]

Последний шаг для экспорта видео

        let exporter = AVAssetExportSession.init(asset: asset, presetName: AVAssetExportPresetMediumQuality)
    exporter?.outputFileType = AVFileTypeQuickTimeMovie
    exporter?.outputURL = outputPath
    exporter?.videoComposition = videoComposition

    exporter?.exportAsynchronously() { handler -> Void in
        if exporter?.status == .completed {
            print("Export complete")
            DispatchQueue.main.async(execute: {
                completion(outputPath)
            })
            return
        } else if exporter?.status == .failed {
            print("Export failed - \(String(describing: exporter?.error))")
        }
        completion(nil)
        return
    }

Это позволит экспортировать видео в квадратном размере с водяным знаком в виде текста или изображения

Спасибо

Просто загрузите код и используйте it.It находится на странице документации разработчика Apple.

Http://developer.apple.com/library/ios/#samplecode/AVSimpleEditoriOS/Listings/AVSimpleEditor_AVSERotateCommand_m.html

Вот пример на swift3, Как вставить в записанное видео как анимированные (массив изображений/слайдов/кадров), так и статические водяные знаки изображения.

Он использует CAKeyframeAnimation для анимации кадров, и он использует AVMutableCompositionTrack, AVAssetExportSession и AVMutableVideoComposition вместе с AVMutableVideoCompositionInstruction, чтобы объединить все вместе.