{"id":278,"date":"2025-11-29T09:07:28","date_gmt":"2025-11-29T01:07:28","guid":{"rendered":"https:\/\/www.52runoob.com\/?p=278"},"modified":"2025-11-29T09:07:28","modified_gmt":"2025-11-29T01:07:28","slug":"%e4%b8%89avfoundation-%e4%b9%8b-%e8%b5%84%e6%ba%90%e5%92%8c%e5%85%83%e6%95%b0%e6%8d%ae%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0_av%e7%9c%9f%e5%ae%9e%e8%b5%84%e6%ba%90","status":"publish","type":"post","link":"https:\/\/www.52runoob.com\/index.php\/2025\/11\/29\/%e4%b8%89avfoundation-%e4%b9%8b-%e8%b5%84%e6%ba%90%e5%92%8c%e5%85%83%e6%95%b0%e6%8d%ae%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0_av%e7%9c%9f%e5%ae%9e%e8%b5%84%e6%ba%90\/","title":{"rendered":"(\u4e09)AVFoundation \u4e4b \u8d44\u6e90\u548c\u5143\u6570\u636e\u5b66\u4e60\u7b14\u8bb0_av\u771f\u5b9e\u8d44\u6e90"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\">(\u4e09) AVFoundation \u2014 \u8d44\u6e90\uff08Asset\uff09\u4e0e\u5143\u6570\u636e\uff08Metadata\uff09\u5b66\u4e60\u7b14\u8bb0\uff08\u542b\u771f\u5b9e\u4ee3\u7801\u793a\u4f8b\uff09<\/h1>\n\n\n\n<p>\u4e0b\u9762\u662f\u4e00\u4efd\u504f\u5b9e\u8df5\u3001\u53ef\u76f4\u63a5\u7c98\u8d34\u8fd0\u884c\u7684 <strong>AVFoundation \u8d44\u6e90\u4e0e\u5143\u6570\u636e\u5b66\u4e60\u7b14\u8bb0<\/strong>\u3002\u5185\u5bb9\u8986\u76d6\uff1a\u5982\u4f55\u6253\u5f00\u672c\u5730\/\u8fdc\u7a0b\u8d44\u6e90\u3001\u5f02\u6b65\u52a0\u8f7d\u5c5e\u6027\u3001\u8bfb\u53d6\u5e38\u89c1\u5143\u6570\u636e\u3001\u5904\u7406 timed metadata\u3001\u63d0\u53d6\u7f29\u7565\u56fe\u3001\u5728\u5bfc\u51fa\u65f6\u5199\u5165\u5143\u6570\u636e\u3001\u4ee5\u53ca\u5e38\u89c1\u5751\/\u89e3\u51b3\u65b9\u6cd5\u3002\u793a\u4f8b\u5747\u4e3a Swift\uff08iOS \/ macOS \u901a\u7528\uff0cSwift 5+\uff09\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u4e00\u3001\u6838\u5fc3\u6982\u5ff5\u901f\u89c8\uff08\u4e00\u53e5\u8bdd\uff09<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>AVAsset \/ AVURLAsset<\/strong>\uff1a\u8868\u793a\u5a92\u4f53\u8d44\u6e90\uff08\u6587\u4ef6\u6216\u8fdc\u7a0b URL\uff09\u3002<\/li>\n\n\n\n<li><strong>AVPlayerItem<\/strong>\uff1a\u7528\u4e8e\u64ad\u653e\u7684\u5c01\u88c5\uff08\u4e5f\u53ef\u89c2\u5bdf timedMetadata\uff09\u3002<\/li>\n\n\n\n<li><strong>AVMetadataItem<\/strong>\uff1a\u8868\u793a\u4e00\u4e2a\u5143\u6570\u636e\u9879\uff08title\u3001artist\u3001creationDate\u3001ID3\/TXXX \u7b49\uff09\u3002<\/li>\n\n\n\n<li><strong>AVAssetExportSession \/ AVAssetWriter<\/strong>\uff1a\u5bfc\u51fa\/\u91cd\u7f16\u7801\u5e76\u53ef\u5199\u5165\u5143\u6570\u636e\u3002<\/li>\n\n\n\n<li><strong>AVAssetImageGenerator<\/strong>\uff1a\u4ece\u89c6\u9891\u4e2d\u622a\u53d6\u7f29\u7565\u56fe\uff08\u5173\u952e\u5e27\uff09\u3002<\/li>\n\n\n\n<li><strong>loadValuesAsynchronously(forKeys:)<\/strong>\uff1a\u5f02\u6b65\u52a0\u8f7d asset \u7684\u5143\u5c5e\u6027\uff08duration\u3001tracks\u3001playable \u7b49\uff09\u3002\u5fc5\u987b\u638c\u63e1\u3002<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u4e8c\u3001\u6253\u5f00\u8d44\u6e90\uff08\u672c\u5730 &amp; \u8fdc\u7a0b\uff09\u4e0e\u201c\u5b89\u5168\u8bfb\u53d6\u201d\u6a21\u5f0f<\/h1>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>\u8981\u70b9<\/strong>\uff1a\u4e0d\u8981\u76f4\u63a5\u7528 <code>asset.duration<\/code> \u2014\u2014 \u8981\u5148\u7528 <code>loadValuesAsynchronously(forKeys:)<\/code> \u52a0\u8f7d\u9700\u8981\u7684\u952e\u5e76\u68c0\u67e5\u72b6\u6001\u3002<\/p>\n<\/blockquote>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nimport AVFoundation\n\nfunc openAsset(url: URL, completion: @escaping (AVAsset?) -&gt; Void) {\n    let asset = AVURLAsset(url: url)\n    let keys = &#x5B;&quot;playable&quot;, &quot;duration&quot;, &quot;tracks&quot;, &quot;commonMetadata&quot;]\n    asset.loadValuesAsynchronously(forKeys: keys) {\n        for k in keys {\n            var err: NSError?\n            let status = asset.statusOfValue(forKey: k, error: &amp;amp;err)\n            if status == .failed || status == .cancelled {\n                print(&quot;\u52a0\u8f7d \\(k) \u5931\u8d25: \\(err?.localizedDescription ?? &quot;unknown&quot;)&quot;)\n                DispatchQueue.main.async { completion(nil) }\n                return\n            }\n        }\n        DispatchQueue.main.async { completion(asset) }\n    }\n}\n\n\/\/ \u672c\u5730\u6587\u4ef6\u793a\u4f8b\nlet fileURL = URL(fileURLWithPath: &quot;\/path\/to\/video.mp4&quot;)\n\n\/\/ \u8fdc\u7a0b\u793a\u4f8b\nlet remoteURL = URL(string: &quot;https:\/\/example.com\/video.mp4&quot;)!\nopenAsset(url: remoteURL) { asset in\n    guard let a = asset else { return }\n    print(&quot;duration: \\(CMTimeGetSeconds(a.duration))&quot;)\n}\n\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u4e09\u3001\u8bfb\u53d6\u5143\u6570\u636e\uff08Metadata\uff09<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">1. <code>commonMetadata<\/code>\uff08\u5e38\u7528\u5feb\u6377\u5b57\u6bb5\uff09<\/h2>\n\n\n\n<p><code>AVAsset.commonMetadata<\/code> \u8fd4\u56de\u4e00\u7ec4 <code>AVMetadataItem<\/code>\u3002\u5e38\u89c1 <code>commonKey<\/code>\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>.title<\/code> \/ <code>.artist<\/code> \/ <code>.albumName<\/code> \/ <code>.creationDate<\/code> \/ <code>.artwork<\/code> \/ <code>.genre<\/code> \/ <code>.description<\/code> \u7b49\u3002<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nfunc printCommonMetadata(from asset: AVAsset) {\n    let meta = asset.commonMetadata\n    for item in meta {\n        if let key = item.commonKey?.rawValue {\n            print(&quot;\\(key): \\(item.value ?? &quot;nil&quot;)&quot;)\n        } else {\n            print(&quot;format:\\(item.identifier ?? &quot;unknown&quot;) value:\\(item.value ?? &quot;nil&quot;)&quot;)\n        }\n    }\n}\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">2. \u6309\u683c\u5f0f\u8bfb\u53d6\uff08\u4f8b\u5982 ID3\u3001iTunes\uff09<\/h2>\n\n\n\n<p>\u5a92\u4f53\u6587\u4ef6\u53ef\u80fd\u6709\u591a\u79cd\u683c\u5f0f\u7684\u5143\u6570\u636e\uff08ID3\u3001iTunes, QuickTime metadata \u7b49\uff09\uff0c\u53ef\u7528\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u8bfb\u53d6 ID3 \u683c\u5f0f\u7684\u5143\u6570\u636e\nlet id3Items = asset.metadata(forFormat: AVMetadataFormat.id3Metadata)\nfor i in id3Items {\n    print(&quot;id3: \\(i.identifier ?? &quot;id&quot;) -&gt; \\(i.value ?? &quot;nil&quot;)&quot;)\n}\n\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">3. \u5e38\u7528 metadata key \u5217\u8868\uff08\u90e8\u5206\uff09<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>AVMetadataKey.commonKeyTitle<\/code> (<code>.title<\/code>)<\/li>\n\n\n\n<li><code>AVMetadataKey.commonKeyArtist<\/code> (<code>.artist<\/code>)<\/li>\n\n\n\n<li><code>AVMetadataKey.commonKeyArtwork<\/code> (<code>.artwork<\/code>) \u2014 value \u4e3a Data\uff08\u901a\u5e38\u662f\u56fe\u50cf\u4e8c\u8fdb\u5236\uff09<\/li>\n\n\n\n<li>QuickTime keys: <code>com.apple.quicktime.creationdate<\/code> \u7b49\u3002<\/li>\n\n\n\n<li>ID3 keys: <code>AVMetadataKey.id3MetadataKeyTitleDescription<\/code> \u7b49\u3002<br>\uff08\u9047\u5230\u4e0d\u8ba4\u8bc6\u7684 key\uff0c\u6253\u5370 <code>identifier<\/code> \u770b\u5230\u5e95\u662f\u4ec0\u4e48\uff09<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u56db\u3001Timed Metadata\uff08\u65f6\u95f4\u8f74\u5143\u6570\u636e\uff09\u2014\u2014\u901a\u5e38\u7528\u4e8e\u76f4\u64ad \/ HLS<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>AVPlayerItem<\/code> \u6709 <code>timedMetadata<\/code>\uff0c\u5f53\u64ad\u653e HLS\uff08.m3u8\uff09\u6216\u5e7f\u64ad\u6d41\u65f6\uff0c\u6d41\u5185\u63d2\u5165\u7684 ID3 \u6807\u7b7e\u6216 in-band \u5143\u6570\u636e\u4f1a\u51fa\u73b0\u5728\u8fd9\u91cc\u3002<\/li>\n\n\n\n<li>\u53ef\u4ee5\u4f7f\u7528 KVO \u6216 <code>addObserver(forKeyPath:)<\/code> \u76d1\u542c <code>timedMetadata<\/code>\u3002\u4f7f\u7528 <code>AVPlayerItem<\/code> \u7684 <code>addObserver<\/code> \u6216 <code>AVPlayerItem<\/code> \u7684 <code>publisher<\/code>\uff08Combine\uff09\u3002<\/li>\n<\/ul>\n\n\n\n<p>\u793a\u4f8b\uff08KVO\uff09\uff1a<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nimport AVKit\n\nvar playerItem: AVPlayerItem!\n\nfunc observeTimedMetadata(of url: URL) {\n    playerItem = AVPlayerItem(url: url)\n    playerItem.addObserver(self, forKeyPath: &quot;timedMetadata&quot;, options: .new, context: nil)\n    let player = AVPlayer(playerItem: playerItem)\n    \/\/ ... \u64ad\u653e\n}\n\noverride func observeValue(forKeyPath keyPath: String?,\n                           of object: Any?,\n                           change: &#x5B;NSKeyValueChangeKey : Any]?,\n                           context: UnsafeMutableRawPointer?) {\n    if keyPath == &quot;timedMetadata&quot; {\n        if let arr = playerItem.timedMetadata {\n            for meta in arr {\n                print(&quot;timed meta: \\(meta)&quot;)\n                \/\/ meta.value \/ meta.stringValue \/ meta.key \/ meta.keySpace\n            }\n        }\n    }\n}\n\n<\/pre><\/div>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u6ce8\u610f\uff1atimedMetadata \u53ea\u6709\u5728\u6d41\u4e2d\u5b58\u5728\u65f6\u624d\u4f1a\u51fa\u73b0\uff0c\u666e\u901a mp4 \u4e0d\u4f1a\u6709\u3002<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u4e94\u3001\u63d0\u53d6\u89c6\u9891\u7f29\u7565\u56fe\uff08AVAssetImageGenerator\uff09<\/h1>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nfunc generateThumbnail(from asset: AVAsset, at time: CMTime = CMTime(seconds: 1, preferredTimescale: 600), completion: @escaping (UIImage?) -&gt; Void) {\n    let gen = AVAssetImageGenerator(asset: asset)\n    gen.appliesPreferredTrackTransform = true \/\/ \u4fee\u6b63\u65cb\u8f6c\n    gen.generateCGImagesAsynchronously(forTimes: &#x5B;NSValue(time: time)]) { _, cgImage, _, result, error in\n        if let cg = cgImage, result == .succeeded {\n            completion(UIImage(cgImage: cg))\n        } else {\n            print(&quot;\u751f\u6210\u7f29\u7565\u56fe\u5931\u8d25: \\(error?.localizedDescription ?? &quot;unknown&quot;)&quot;)\n            completion(nil)\n        }\n    }\n}\n\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u516d\u3001\u5728\u5bfc\u51fa\u65f6\u5199\u5165 \/ \u4fee\u6539\u5143\u6570\u636e\uff08AVAssetExportSession\uff09<\/h1>\n\n\n\n<p>\u573a\u666f\uff1a\u4f60\u7528 <code>AVAssetExportSession<\/code> \u5bfc\u51fa\u89c6\u9891\u5e76\u60f3\u5199\u5165 title\u3001artist\u3001cover\u3002<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nfunc exportWithMetadata(asset: AVAsset, outputURL: URL, completion: @escaping (Bool) -&gt; Void) {\n    guard let export = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {\n        completion(false); return\n    }\n    export.outputURL = outputURL\n    export.outputFileType = .mp4\n\n    \/\/ \u6784\u5efa AVMutableMetadataItem\n    let title = AVMutableMetadataItem()\n    title.keySpace = .common\n    title.key = AVMetadataKey.commonKeyTitle as NSString\n    title.value = &quot;My Title&quot; as NSString\n\n    let artist = AVMutableMetadataItem()\n    artist.keySpace = .common\n    artist.key = AVMetadataKey.commonKeyArtist as NSString\n    artist.value = &quot;My Artist&quot; as NSString\n\n    \/\/ \u5c01\u9762\uff08UIImage -&gt; Data\uff09\n    if let image = UIImage(named: &quot;cover&quot;), let data = image.pngData() {\n        let artwork = AVMutableMetadataItem()\n        artwork.keySpace = .common\n        artwork.key = AVMetadataKey.commonKeyArtwork as NSString\n        artwork.value = data as NSData\n        export.metadata = &#x5B;title, artist, artwork]\n    } else {\n        export.metadata = &#x5B;title, artist]\n    }\n\n    export.exportAsynchronously {\n        switch export.status {\n        case .completed:\n            completion(true)\n        default:\n            print(&quot;\u5bfc\u51fa\u5931\u8d25\uff1a\\(String(describing: export.error))&quot;)\n            completion(false)\n        }\n    }\n}\n\n<\/pre><\/div>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u6ce8\u610f\uff1a\u4e0d\u540c\u7684 <code>outputFileType<\/code> \u5bf9 metadata \u7684\u652f\u6301\u4e0d\u540c\uff08.mov \u5f3a\u4e8e .mp4 \u67d0\u4e9b\u5bb9\u5668\uff09\u3002\u82e5 metadata \u65e0\u6548\uff0c\u5c1d\u8bd5\u6539\u4e3a <code>.mov<\/code> \u6216\u4f7f\u7528 <code>AVAssetWriter<\/code> \u66f4\u7ec6\u81f4\u5730\u5199\u5165\u3002<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u4e03\u3001\u5199\u5165\u590d\u6742\u5143\u6570\u636e \u2014 AVAssetWriter\uff08\u66f4\u5e95\u5c42\uff09<\/h1>\n\n\n\n<p><code>AVAssetExportSession<\/code> \u80fd\u6ee1\u8db3\u5e38\u89c1\u573a\u666f\uff1b\u82e5\u9700\u66f4\u590d\u6742\u63a7\u5236\uff08\u5982\u9010\u5e27\u6ce8\u5165 timed metadata\u3001\u6216\u5c06\u97f3\u9891\/\u89c6\u9891\u91cd\u7f16\u7801\u5e76\u5199\u5165 ID3\uff09\uff0c\u5219\u4f7f\u7528 <code>AVAssetWriter<\/code> + <code>AVAssetWriterInputMetadataAdaptor<\/code>\u3002<\/p>\n\n\n\n<p>\u793a\u4f8b\u7565\u590d\u6742\uff0c\u6838\u5fc3\u662f\u521b\u5efa <code>AVAssetWriter<\/code>, \u6dfb\u52a0 <code>AVAssetWriterInput<\/code>\uff08video\/audio\uff09\uff0c\u5e76\u521b\u5efa <code>AVAssetWriterInputMetadataAdaptor<\/code> \u6765\u5199 timed-metadata\uff08\u53c2\u89c1\u5b98\u65b9\u6587\u6863\uff09\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u516b\u3001\u5e38\u89c1\u95ee\u9898\u4e0e\u5751\uff08\u52a1\u5fc5\u8bb0\u4f4f\uff09<\/h1>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u540c\u6b65\u8bbf\u95ee asset \u7684\u5c5e\u6027\u4f1a\u963b\u585e\u6216\u8fd4\u56de 0<\/strong>\n<ul class=\"wp-block-list\">\n<li>\u5fc5\u987b\u4f7f\u7528 <code>loadValuesAsynchronously(forKeys:)<\/code> \u6216 <code>AVAssetResourceLoader<\/code> \u7684\u56de\u8c03\u52a0\u8f7d\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>\u8fdc\u7a0b URL\uff08HTTP\uff09\u9700\u8981 Content-Type \u4e0e range \u652f\u6301<\/strong>\n<ul class=\"wp-block-list\">\n<li>HLS \/ progressive download \u5bf9\u670d\u52a1\u5668\u652f\u6301 range \u4e0e\u5b57\u8282\u670d\u52a1\u4f9d\u8d56\u6027\u5927\uff0c\u82e5\u670d\u52a1\u5668\u4e0d\u652f\u6301 Range\uff0c\u4f1a\u5bfc\u81f4\u65e0\u6cd5\u64ad\u653e\u6216\u65e0\u6cd5 seek\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>metadata \u5199\u5165\u5bb9\u5668\u517c\u5bb9\u6027<\/strong>\n<ul class=\"wp-block-list\">\n<li>\u5e76\u975e\u6240\u6709\u5bb9\u5668\u90fd\u652f\u6301\u5199\u5165\u4efb\u610f metadata\uff1b\u4f8b\u5982\u90e8\u5206 MP4 \u64ad\u653e\u5668\u4e0d\u4f1a\u663e\u793a\u6240\u6709 commonMetadata\u3002\u5c1d\u8bd5 <code>.mov<\/code> \u6216\u4f7f\u7528 <code>atomic: true<\/code> \u7684\u65b9\u6cd5\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>\u4f5c\u54c1\u6c34\u5370\/\u7b7e\u540d\/\u9632\u7be1\u6539<\/strong>\n<ul class=\"wp-block-list\">\n<li>\u6709\u7684 App \u5728\u8fd0\u884c\u65f6\u4f1a\u6821\u9a8c\u539f\u59cb\u6587\u4ef6\u7684 hash \/ signature\uff0c\u91cd\u6253\u5305\u6216\u4fee\u6539\u540e\u4f1a\u6821\u9a8c\u5931\u8d25\uff08\u9700 patch \u6821\u9a8c\u903b\u8f91\u6216\u5728\u670d\u52a1\u7aef\u5904\u7406\uff09\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>timedMetadata \u53ea\u6709\u5728\u6d41\u91cc\u5b58\u5728<\/strong>\n<ul class=\"wp-block-list\">\n<li>\u4e0d\u662f\u6240\u6709\u8d44\u6e90\u90fd\u5305\u542b timed metadata\uff0c\u901a\u5e38 HLS \u7684 ID3 \u624d\u4f1a\u51fa\u73b0\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>\u827a\u672f\u5bb6\/\u5c01\u9762\u8bfb\u53d6\u4e3a Data<\/strong>\n<ul class=\"wp-block-list\">\n<li>artwork \u7684 <code>value<\/code> \u5e38\u4e3a <code>Data<\/code> \u6216 <code>NSData<\/code>\uff0c\u9700\u8981\u89e3\u7801\u6210\u56fe\u7247\u683c\u5f0f\uff08PNG\/JPEG\uff09\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>ID3\/RIFF\/QuickTime KeySpace \u533a\u5206<\/strong>\n<ul class=\"wp-block-list\">\n<li>\u540c\u6837\u7684 \u201cTitle\u201d \u53ef\u80fd\u5b58\u5728\u4e8e\u4e0d\u540c keySpace\uff08id3, iTunes, quicktime\uff09\uff0c\u5fc5\u8981\u65f6\u9010\u4e2a\u683c\u5f0f\u68c0\u67e5\uff1a<code>asset.metadata(forFormat:)<\/code>\u3002<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>AVAssetExportSession \u7684 metadata \u8986\u76d6\u884c\u4e3a<\/strong>\n<ul class=\"wp-block-list\">\n<li>\u6709\u65f6\u53ea\u662f\u5408\u5e76\u800c\u975e\u8986\u76d6\uff0c\u6216\u88ab\u64ad\u653e\u5668\u5ffd\u7565\u3002\u9a8c\u8bc1\u5bfc\u51fa\u540e\u7684\u6587\u4ef6\u5b9e\u9645\u5305\u542b\u7684 metadata\uff1a\u53ef\u7528 <code>AVAsset(url:).commonMetadata<\/code> \u518d\u6b21\u8bfb\u53d6\u3002<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u4e5d\u3001\u5b9e\u7528\u5de5\u5177\u51fd\u6570\u5408\u96c6\uff08\u5feb\u901f\u590d\u5236\uff09<\/h1>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\/\/ \u8bfb\u53d6 asset \u7684\u6240\u6709 metadata\uff08\u6309 format\uff09\nfunc dumpAllMetadata(asset: AVAsset) {\n    let formats = asset.availableMetadataFormats\n    print(&quot;formats: \\(formats)&quot;)\n    for f in formats {\n        let items = asset.metadata(forFormat: f)\n        print(&quot;== format: \\(f) count: \\(items.count)&quot;)\n        for i in items {\n            print(&quot;id:\\(i.identifier ?? &quot;nil&quot;) key:\\(String(describing:i.key)) keyspace:\\(i.keySpace?.rawValue ?? &quot;nil&quot;) value:\\(String(describing:i.value))&quot;)\n        }\n    }\n}\n\n\/\/ \u5c06 AVMetadataItem.value \u8f6c\u4e3a String\nfunc metadataValueString(_ item: AVMetadataItem) -&gt; String? {\n    if let s = item.stringValue { return s }\n    if let num = item.numberValue { return num.stringValue }\n    if let data = item.dataValue, let str = String(data: data, encoding: .utf8) { return str }\n    return nil\n}\n\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u5341\u3001\u793a\u4f8b\uff1a\u5b8c\u6574\u5de5\u4f5c\u6d41\uff08\u8bfb\u53d6 \u2192 \u751f\u6210\u7f29\u7565\u56fe \u2192 \u5bfc\u51fa\u5e76\u5199\u5165 metadata\uff09<\/h1>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nlet url = URL(string: &quot;https:\/\/example.com\/video.mp4&quot;)!\nopenAsset(url: url) { asset in\n    guard let asset = asset else { return }\n    \/\/ 1. \u6253\u5370 commonMetadata\n    printCommonMetadata(from: asset)\n    \/\/ 2. \u751f\u6210\u7f29\u7565\u56fe\n    generateThumbnail(from: asset, at: CMTime(seconds: 2, preferredTimescale: 600)) { image in\n        \/\/ \u4fdd\u5b58\u4e34\u65f6\u5c01\u9762\n        if let img = image, let data = img.pngData() {\n            let tmp = FileManager.default.temporaryDirectory.appendingPathComponent(&quot;cover.png&quot;)\n            try? data.write(to: tmp)\n            \/\/ 3. \u5bfc\u51fa\u5e76\u5199\u5165 metadata\uff08\u5c06\u5c01\u9762\u5199\u5165\uff09\n            let out = FileManager.default.temporaryDirectory.appendingPathComponent(&quot;out.mp4&quot;)\n            exportWithMetadata(asset: asset, outputURL: out) { ok in\n                print(&quot;\u5bfc\u51fa\u5b8c\u6210\uff1a\\(ok) -&gt; \\(out.path)&quot;)\n                \/\/ \u9a8c\u8bc1\n                openAsset(url: out) { newAsset in\n                    printCommonMetadata(from: newAsset!)\n                }\n            }\n        }\n    }\n}\n\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">\u5341\u4e00\u3001\u8fdb\u9636\u4e0e\u63a8\u8350\u9605\u8bfb<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u5b66\u4e60 <code>AVAssetResourceLoaderDelegate<\/code>\uff1a\u5b9e\u73b0\u81ea\u5b9a\u4e49\u8d44\u6e90\u52a0\u8f7d\uff08DRM \/ \u79bb\u7ebf\u7f13\u5b58 \/ \u81ea\u5b9a\u4e49\u534f\u8bae\uff09\u3002<\/li>\n\n\n\n<li><code>AVAssetWriter<\/code> + <code>AVAssetReader<\/code>\uff1a\u7528\u4e8e\u8f6c\u7801\u4e0e\u66f4\u7ec6\u7c92\u5ea6\u5199\u5165\u3002<\/li>\n\n\n\n<li>timed metadata \u4e0e HLS \u6d41\u5904\u7406\uff08ID3 \u4e0e SCTE \u6807\u51c6\uff09\u3002<\/li>\n\n\n\n<li>Apple \u5b98\u65b9 AVFoundation Programming Guide\uff08\u82f1\u6587\uff09\u4e0e Sample Code\uff08\u53ef\u5728 Apple \u5f00\u53d1\u8005\u7ad9\u70b9\u67e5\u9605\uff09\u3002<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>(\u4e09) AVFoundation \u2014 \u8d44\u6e90\uff08Asset\uff09\u4e0e\u5143\u6570\u636e\uff08Metadat&#8230; <a class=\"more-link\" href=\"https:\/\/www.52runoob.com\/index.php\/2025\/11\/29\/%e4%b8%89avfoundation-%e4%b9%8b-%e8%b5%84%e6%ba%90%e5%92%8c%e5%85%83%e6%95%b0%e6%8d%ae%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0_av%e7%9c%9f%e5%ae%9e%e8%b5%84%e6%ba%90\/\">Continue Reading &rarr;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12],"tags":[],"class_list":["post-278","post","type-post","status-publish","format-standard","hentry","category-12"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/posts\/278","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/comments?post=278"}],"version-history":[{"count":1,"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/posts\/278\/revisions"}],"predecessor-version":[{"id":279,"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/posts\/278\/revisions\/279"}],"wp:attachment":[{"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/media?parent=278"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/categories?post=278"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.52runoob.com\/index.php\/wp-json\/wp\/v2\/tags?post=278"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}