AVPlayer/MPMoviePlayerController的字幕

16

我正在使用m3u8视频格式来流式传输视频,现在需要显示相应的字幕。

我在苹果文档中搜索到了使用AVPlayerclosedCaptionDisplayEnabled属性可以实现此功能。

我想知道字幕的格式应该是什么?使用.srt格式是否可行?

同时,我能否使用MPMoviePlayerController来实现同样的功能?

任何帮助都将不胜感激。


1
请参考以下链接:https://dev59.com/nmgu5IYBdhLWcg3wdGwb 和 https://dev59.com/PVbTa4cB1Zd3GeqP8je_。 - Till
苹果文档在这一点上的措辞非常准确。它仅表示 closedCaptionDisplayEnabled“指示播放器是否使用闭路字幕”,并未提及任何关于字幕的内容。在我的测试中(在 OS X 上),该属性对字幕没有影响;它适用于闭路字幕。(您可以通过使用 EyeTV 录制带有闭路字幕的电视节目,然后将其导出到 iPhone 和 MPEG-2 复合格式,最后使用 HandBrake 转换后进行自行测试。EyeTV 到 iPhone 的导出将具有 CC 轨道;HandBrake 转换后将具有字幕轨道。) - Peter Hosey
6个回答

36

更新于03/06/2020: 在 GitHub 上,jbweimar 创建了一个使用 AVAssetResourceLoaderDelegate 方法的示例项目,看起来非常有前途:https://github.com/jbweimar/external-webvtt-example


更新于10/30/2018: 值得注意的是查看苹果工程师的这个答案(感谢@allenlini提供的提示)。 他建议采用涉及 AVAssetResourceLoaderDelegate 的解决方案。我自己没有尝试过,但它可能比下面我的解决方案更好。


原始回答:

似乎在 m3u8 流描述中引用 WebVTT 文件是官方支持的方式。事实上,在后期添加它们似乎不受官方支持(请参见一位苹果工程师在本页底部发表的声明)。

然而,这并不意味着你不能让它工作 ;-)。借助这个精彩的演示文稿和 Chris Adamson 的示例项目(ZIP)苹果开发者论坛上的这篇帖子以及 Abdul Azeem 的这篇 Ray Wenderlich 教程,我成功地让它工作了。这是 Abdul Azeem 示例代码和 Chris 的示例项目的修改版本。

请注意,需要使用 AVMediaTypeText 而不是 AVMediaTypeSubtitle。这似乎是 iOS 中的一个错误。

// 1 - Load video asset
AVAsset *videoAsset = [AVURLAsset assetWithURL:[[NSBundle mainBundle] URLForResource:@"video" withExtension:@"mp4"]];

// 2 - Create AVMutableComposition object. This object will hold your AVMutableCompositionTrack instances.
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];

// 3 - Video track
AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
                                                                    preferredTrackID:kCMPersistentTrackID_Invalid];
[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
                    ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
                     atTime:kCMTimeZero error:nil];

// 4 - Subtitle track
AVURLAsset *subtitleAsset = [AVURLAsset assetWithURL:[[NSBundle mainBundle] URLForResource:@"subtitles" withExtension:@"vtt"]];

AVMutableCompositionTrack *subtitleTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeText
                                                                       preferredTrackID:kCMPersistentTrackID_Invalid];

[subtitleTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
                       ofTrack:[[subtitleAsset tracksWithMediaType:AVMediaTypeText] objectAtIndex:0]
                        atTime:kCMTimeZero error:nil];

// 5 - Set up player
AVPlayer *player = [AVPlayer playerWithPlayerItem: [AVPlayerItem playerItemWithAsset:mixComposition]];

非常感谢您的精彩文章。请问如何设置元数据?let titleMetadataItem = AVMutableMetadataItem()titleMetadataItem.locale = NSLocale.currenttitleMetadataItem.key = AVMetadataCommonKeyTitle as (NSCopying & NSObjectProtocol)?titleMetadataItem.keySpace = AVMetadataKeySpaceCommontitleMetadataItem.value = self.assetObject.title as (NSCopying & NSObjectProtocol)? - Taimur Ajmal
1
@Johannes Fahrenkrug:这个解决方案能否适用于远程m3u8播放列表中的真实HLS流,请问?我正在尝试使用Swift实现,但没有成功。您能否请检查一下我的帖子:https://dev59.com/OVgQ5IYBdhLWcg3wMhBJ。谢谢! - Martin Koles
2
这里有一篇不错的文章,标题为“将独立的WebVTT文件中的字幕添加到AVAsset/PlayerItem中”,链接为https://forums.developer.apple.com/thread/45060。 - allenlinli
1
@allenlinli 哇,这是一个很棒的解决方案!谢谢分享!! - Johannes Fahrenkrug
1
@m_katsifarakis 很酷!是的,请发布更新!还要确保查看此链接:https://forums.developer.apple.com/thread/45060#202064 - Johannes Fahrenkrug
显示剩余9条评论

7

这是@JohannesFahrenkrug的答案的Swift版本。希望对某些人有用:

    let localVideoAsset = Bundle.main.url(forResource: "L1C1P1C3", withExtension: "mp4")

    //Create AVMutableComposition
    let videoPlusSubtitles = AVMutableComposition()

    //Adds video track
    let videoTrack = videoPlusSubtitles.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)

    try? videoTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, localVideoAsset.duration),
                                of: localVideoAsset.tracks(withMediaType: .video)[0],
                                at: kCMTimeZero)

    //Adds subtitle track
    let subtitleAsset = AVURLAsset(url: Bundle.main.url(forResource: "L1C1P1C3", withExtension: ".mp4.vtt")!)

    let subtitleTrack = videoPlusSubtitles.addMutableTrack(withMediaType: .text, preferredTrackID: kCMPersistentTrackID_Invalid)

    try? subtitleTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, localVideoAsset.duration),
                                        of: subtitleAsset.tracks(withMediaType: .text)[0],
                                        at: kCMTimeZero)

我对URL localVideoAsset如何具有duration成员感到困惑? - SafeFastExpressive

2

添加字幕时出现音频错误。更新版本如下;

注意:仅支持将本地项目(资产)插入到AVMutableComposition中,在iOS 15之前,无法使用远程项目(例如HTTP视频流)。

视频和字幕文件

将HTTP流插入到AVMutableComposition中

Swift 5

func play() {
    guard let videoUrl = Bundle.main.url(forResource: "ElephantsDream", withExtension: "mp4")?.absoluteURL,
          let subtitleUrl = Bundle.main.url(forResource: "subtitles-en", withExtension: "vtt")?.absoluteURL else {
              return
          }


    let videoAsset = AVURLAsset(url: videoUrl)
    let subtitleAsset = AVURLAsset(url: subtitleUrl)

    // Create AVMutableComposition object. This object will hold your AVMutableCompositionTrack instances.
    let mixComposition = AVMutableComposition()

    let videoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
    let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
    let subtitleTrack = mixComposition.addMutableTrack(withMediaType: .text, preferredTrackID: kCMPersistentTrackID_Invalid)

    do {
        try videoTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                        of: videoAsset.tracks(withMediaType: .video).first!,
                                        at: .zero)
        try audioTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                        of: videoAsset.tracks(withMediaType: .audio).first!,
                                        at: .zero)
        try subtitleTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: subtitleAsset.duration),
                                           of: subtitleAsset.tracks(withMediaType: .text).first!, at: .zero)
    } catch let err {
        print(err.localizedDescription)
    }

    let player = AVPlayer(playerItem: AVPlayerItem(asset: mixComposition))
    let vc = AVPlayerViewController()
    vc.player = player
    present(vc, animated: true) {
        vc.player?.play()
    }
}

如果您想使用服务器端视频和字幕,需要像这样使用。
iOS 15
func play() {
    if #available(iOS 15.0.0, *) {
        let videoUrl = URL(string: "https://wwww.acme.com/video/elephantsdream.mov")!
        let subtitleUrl = URL(string: "https://wwww.acme.com/subtitle/subtitles-en.vtt")!
        
        let videoAsset = AVURLAsset(url: videoUrl)
        let subtitleAsset = AVURLAsset(url: subtitleUrl)

        let mixComposition = AVMutableComposition()
        let videoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
        let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
        let subtitleTrack = mixComposition.addMutableTrack(withMediaType: .text, preferredTrackID: kCMPersistentTrackID_Invalid)

        Task {
            if let videoTrackItem = try await videoAsset.loadTracks(withMediaType: .video).first {
                try videoTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                                of: videoTrackItem,
                                                at: .zero)
            }
            if let audioTrackItem = try await videoAsset.loadTracks(withMediaType: .audio).first {
                try audioTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                                of: audioTrackItem,
                                                at: .zero)
            }
            if let subtitleTrackItem = try await subtitleAsset.loadTracks(withMediaType: .text).first {
                try subtitleTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                                   of: subtitleTrackItem,
                                                   at: .zero)
            }
            DispatchQueue.main.async {
                let player = AVPlayer(playerItem: AVPlayerItem(asset: mixComposition))
                let vc = AVPlayerViewController()
                vc.player = player
                self.present(vc, animated: true) {
                    vc.player?.play()
                }
            }
        }
    }
}

这对我不起作用,视频和音频都可以,但字幕没有显示(我的字幕是.srt格式),我的视频有两个音轨,你知道如何切换到我的第二个音轨吗? - Amin Rezaew
我已经将字幕添加为VTT格式,你可以试试这种方式吗?对于多个音频文件,你可以尝试从视频列表中添加,像这样:videoAsset.loadTracks(withMediaType: .audio).first - perpeer
当我将我的SRT字幕更改为VTT时,这段代码就能够正常工作了,请编辑并将其添加到您的答案中,谢谢=) - Amin Rezaew
字幕有黑色背景,你知道怎么删除吗?还有,能改变字幕的颜色吗? - Amin Rezaew

2

AVPlayer和MPMoviePlayerController都可以显示字幕。

区别在于,使用AVPlayer时,您可以使用closedCaptionDisplayEnabled属性控制是否显示字幕。

而在MPMoviePlayerController中,用户可以通过设置应用程序中的开关来控制是否显示字幕。您无法在应用程序中进行编程控制。


2
closedCaptionDisplayEnabled 切换的是闭路字幕,而不是字幕。它们是两个不同的东西,该属性对字幕没有影响。(一个注意点:我只在 OS X 上尝试过这个,而没有在 iOS 上尝试过。) - Peter Hosey

0

WWDC 2013 iOS应用程序使用WebVTT文件作为其字幕(感谢Nicholas Riley发现此问题)。由于某种原因,每个会话大约有50个(确切数字不同)。

我不知道AVFoundation或MPMoviePlayer级别是否支持WebVTT,或者应用程序是否下载并解析字幕文件。

快速搜索“webvtt m3u8”会出现对此HTTP Live Streaming Draft的引用,该草案建议您只需在m3u8播放列表中引用WebVTT文件即可使其正常工作。但是,我没有为您提供示例,因为我无法轻松猜测WWDC会话的m3u8 URL。


0
如果有人需要最新的Swift和自定义的SwiftUI视图(为SwiftUI包装的AVplayerController),这是更改,感谢@perpeer。
let videoAsset = AVURLAsset(url: videoUrl)
    let subtitleAsset = AVURLAsset(url: subtitleUrl)
    
    let mixComposition = AVMutableComposition()
    let videoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
    let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
    let subtitleTrack = mixComposition.addMutableTrack(withMediaType: .text, preferredTrackID: kCMPersistentTrackID_Invalid)
    
    Task {
        if let videoTrackItem = try await videoAsset.loadTracks(withMediaType: .video).first {
            try await videoTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.load(.duration)),
                                            of: videoTrackItem,
                                            at: .zero)
        }
        if let audioTrackItem = try await videoAsset.loadTracks(withMediaType: .audio).first {
            try await audioTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.load(.duration)),
                                            of: audioTrackItem,
                                            at: .zero)
        }
        if let subtitleTrackItem = try await subtitleAsset.loadTracks(withMediaType: .text).first {
            try await subtitleTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.load(.duration)),
                                               of: subtitleTrackItem,
                                               at: .zero)
        }
        DispatchQueue.main.async {
            let player = AVPlayer(playerItem: AVPlayerItem(asset: mixComposition))
            let vc = AVPlayerViewController()
            vc.player = player
            self.present(vc, animated: true) {
                vc.player?.play()
            }
        }
    }

尝试了本地和远程的URL,都可以正常工作,关键是你的字幕必须使用.vtt扩展名。

不要将GCD与async/await混合使用。 - undefined
@loremipsum 我并不想改变perpeer的回答中的任何内容,在我的代码中我没有使用GCD,它可以正常工作。 - undefined
那个 DispatchQueue 不应该在 Task 里面。它们也是不正确的,你的只是今天显示的一个。DispatchQueue 用于线程,而 Task 用于 actor,你应该在 MainActor 上进行调用,而不是在主线程上。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接