美文网首页音视频知识库
FFmpeg iOS开发配置和格式转换

FFmpeg iOS开发配置和格式转换

作者: rome753 | 来源:发表于2022-06-27 10:46 被阅读0次

    https://www.jianshu.com/p/ecfbebadbe55
    集成方法这篇文章写得非常详细了,我就不再赘述。

    有一点差异的地方,该文章集成后使用的是OC进行调用,我使用的是C语言,这样可以方便跨平台。因为FFmpeg本身是C语言的,我先用C封装成工具类,这样不依赖平台,上层用什么语言调都行。

    这里实现一个简单的FFmpeg命令行格式转换功能。

    1 创建C文件

    新建my_test.c和头文件,在这个头文件里导入#import "fftools/ffmpeg.h",因为ffmpeg.h头文件里导入了所有的模块,就不需要外面一个个导入了。

    
    #ifndef my_test_h
    #define my_test_h
    
    #include <stdio.h>
    //#import "libavformat/avformat.h"
    #import "fftools/ffmpeg.h"
    
    char* do_test(char* s);
    char* do_cmd(char* s);
    
    #endif /* my_test_h */
    

    然后新建一个Demo-Bridging-Header.h头文件,导入上面创建的my_test.h的头文件

    #ifndef Demo_Bridging_Header_h
    #define Demo_Bridging_Header_h
    
    //#import "libavformat/avformat.h"
    #import "my_test.h"
    
    #endif /* Demo_Bridging_Header_h */
    

    添加到工程配置里,这样就可以在Swift里调用了

    截屏2022-06-27 10.05.44.png

    2 Swift与C字符串转换

    要在Swift里使用C库,就要把文件路径传给C,接收C返回的结果。Swift里面的字符串是String,C里面一般是char*,它们之间相互转换方法如下(转换都是在Swift里进行的)。

    • String转char*
            let cmd = "ffmpeg -y -f gif -i \(src) \(dst)"
            let pointer = UnsafeMutablePointer<CChar>(mutating: cmd.cString(using: .utf8))
    
    • char*转String
            String.init(cString: ret)
    

    3 FFmpeg命令行

    在my_test.c里添加调用FFmpeg命令行的方法,上层直接传入完整的FFmpeg命令行str,用空格进行字符串分割后转换成字符串数组char *argv[50],调用ffmpeg_main方法处理。

    char* do_cmd(char* str) {
        char delim[] = " ";
    
        char *ptr = strtok(str, delim);
    
        int argc = 0;
        char *argv[50];
        
        while(ptr != NULL)
        {
            argv[argc++] = ptr;
            ptr = strtok(NULL, delim);
        }
        
        for (int i = 0; i < argc; i++) {
            printf("%s\n", argv[i]);
        }
        
        ffmpeg_main(argc, argv);
        
        return "";
    }
    

    4 Swift调用

    将命令行cmd转换成UnsafeMutablePointer<CChar>后,直接调用C里面的do_cmd方法即可。

            let cmd = "ffmpeg -y -f gif -i \(src) \(dst)"
            print("cmd: \(cmd)")
            let pointer = UnsafeMutablePointer<CChar>(mutating: cmd.cString(using: .utf8))
            print("ffmpeg begin ---------------------")
            
            if let ret = do_cmd(pointer) {
                print(String.init(cString: ret))
                print("ffmpeg end   ---------------------")
                UISaveVideoAtPathToSavedPhotosAlbum(dst, self, #selector(self.didFinishSavingVideo(videoPath:error:contextInfo:)), nil)
            }
    

    5 完整功能

    点击按钮,从手机相册里选一张gif动图,转换成mp4视频,保存到相册。

    
    import UIKit
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            
            let label = UILabel(frame: CGRect(x: 50, y: 50, width: 200, height: 100))
            label.text = "ffmpeg "
            label.backgroundColor = .white.withAlphaComponent(0.2)
            view.addSubview(label)
            
            label.isUserInteractionEnabled = true
            label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(pickImage)))
        }
    }
    
    // MARK: - 图片操作
    extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        
        @objc func pickImage() {
            self.present(imagePickerController, animated: true)
        }
        
        // MARK: 图片选择器界面
        var imagePickerController: UIImagePickerController {
            let imagePicket = UIImagePickerController()
            imagePicket.delegate = self
            imagePicket.sourceType = .photoLibrary
            imagePicket.allowsEditing = false
            return imagePicket
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            picker.dismiss(animated: true, completion: nil)
            guard let image = info[UIImagePickerController.InfoKey(rawValue: UIImagePickerController.InfoKey.originalImage.rawValue)] as? UIImage else {
                print("pick_image_fail")
                return
            }
            let path = (info[UIImagePickerController.InfoKey(rawValue: UIImagePickerController.InfoKey.imageURL.rawValue)] as? URL)?.path ?? ""
            
            onImagePicked(image, path)
        }
        
        private func onImagePicked(_ image: UIImage?, _ path: String) {
    //        let fileName = "\(CLongLong(round(Date().timeIntervalSince1970*1000))).mp4"
    //        let fileName = "d.mp4"
            let src = NSTemporaryDirectory() + "1.gif"
            let dst = NSTemporaryDirectory() + "2.mp4"
            do {
                try FileManager.default.removeItem(atPath: src)
            } catch {
                print(error)
            }
            do {
                try FileManager.default.moveItem(atPath: path, toPath: src)
            } catch {
                print(error)
            }
            
            FileManager.default.createFile(atPath: dst, contents: nil)
            
    //        do {
    //            try FileManager.default.removeItem(atPath: dst)
    //        } catch _{}
            
            let cmd = "ffmpeg -y -f gif -i \(src) \(dst)"
            print("cmd: \(cmd)")
            let pointer = UnsafeMutablePointer<CChar>(mutating: cmd.cString(using: .utf8))
            print("ffmpeg begin ---------------------")
            
            if let ret = do_cmd(pointer) {
                print(String.init(cString: ret))
                print("ffmpeg end   ---------------------")
                UISaveVideoAtPathToSavedPhotosAlbum(dst, self, #selector(self.didFinishSavingVideo(videoPath:error:contextInfo:)), nil)
            }
        }
    
        // MARK: 当点击图片选择器中的取消按钮时回调
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            picker.dismiss(animated: true, completion: nil)
        }
    
        
        @objc func didFinishSavingVideo(videoPath: String, error: NSError?, contextInfo: UnsafeMutableRawPointer?) {
            if error != nil{
                print("保存失败")
            }else{
                print("保存成功,请到相册中查看")
            }
        }
    }
    
    

    有两个小问题:

    • 转换的目标文件如果不存在会报错,但是提前创建了命令行又会询问是否覆盖原文件。只能先创建,然后再命令行里加上-y,代表自动确认覆盖。

    • 传给ffmpeg的文件全路径名不能太长,太长会直接报错文件找不到,然而iOS里文件全路径名又都很长。我能想到的方法是在临时文件目录里创建两个临时文件,名字很短,先把文件复制到这里,再进行转换。不知道有没有更好的方法。

    相关文章

      网友评论

        本文标题:FFmpeg iOS开发配置和格式转换

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