48517

[code]

    stopButton.isEnabled = false
    print("ループの前")
    for _ in 0...(startPoint) {
        sample = readerOutput.copyNextSampleBuffer()
        while reader.status != AVAssetReaderStatus.reading {
            sleep(UInt32(0.1))
        }
    
    }
    print("ループの後")
    
[/code]

上記のようなソースですが、  
ビデオファイルからサンプルバッファを取り出すループで、  
ビデオの頭出しをしています。  
このループの前でstopButtonを無効にしても、  
ボタンが有効のままです。  
ループが終了したところで無効になります。

ループの前でちゃんと無効化する方法はないでしょうか。  
よろしくご教示のほどお願いします。

===========================

[code]

    let avAsset = AVURLAsset(url: fileURL, options: options)
    var reader: AVAssetReader! = nil
    do {
        reader = try AVAssetReader(asset: avAsset)
    } catch {
        #if DEBUG
        print("could not initialize reader.")
        #endif
        return
    }
    guard let videoTrack = avAsset.tracks(withMediaType: AVMediaType.video).last else {
        #if DEBUG
        print("could not retrieve the video track.")
        #endif
        return
    }
    let readerOutputSettings: [String: Any] = 
    [kCVPixelBufferPixelFormatTypeKey as String : 
    Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)]
    let readerOutput = AVAssetReaderTrackOutput(track: videoTrack, 
    outputSettings: readerOutputSettings)
    reader.add(readerOutput)
    reader.startReading()
    
    var sample:CMSampleBuffer!        
    stopButton.isEnabled=false
    
    for _ in 0...(startPoint) {
        sample = readerOutput.copyNextSampleBuffer()
        while reader.status != AVAssetReaderStatus.reading {
            sleep(UInt32(0.1))
        }
    }
    
[/code]

ちょっと前のコードから書いてみました。  
startPoint はビデオのフレーム数で、その数値を直接設定できないか  
色々と探したんですが、見つける事が出来ず、  
不甲斐ないことに、そのフレーム数まで空回りさせています。

ご指摘頂いた timerange は  
フレーム数の直接設定のヒントかと思い、  
reader もしくは readerOutput のなかに  
timerange に関する何かないか探してみましたが  
上手く解決出来ませんでした。  
よろしくお願いします。

dispatchQuene  
でちゃんと？動いてはいます。  
ですが、ループ後の処理で（そのコードは書いていませんが）  
main thread でしかやってはいけないよと  
warningが出ていてます。  
この解決も上手くいってはいません。


CC BY-SA 4.0
2018-09-18T03:16:01.210
48491
2018-09-19T05:56:59.620
2018-09-18T14:38:01.420
30151
30151
question
swift ios
ボタンの無効化が上手くいきません。


Main Threadでループ処理をせず、ループ処理をGCD等でBackground Threadで行うとうまくいくと思います。  
以下のような感じでいかがでしょうか？

[code]

    stopButton.isEnabled = false
    
    DispatchQueue.global(qos: .background).async {
        print("ループの前")
        for _ in 0...(startPoint) {
            sample = readerOutput.copyNextSampleBuffer()
            while reader.status != AVAssetReaderStatus.reading {
                sleep(UInt32(0.1))
            }
    
        }
        print("ループの後")
    }
    
[/code]


CC BY-SA 4.0
2018-09-18T04:04:08.577
48493
2018-09-18T04:04:08.577
17014
48491
answer


とりあえず、`timeRange`を使うコード例を示しておきます。`let startTime =
...`がフレーム数から`CMTime`を求めている(*)部分になります。

[code]

        //...
        reader.add(readerOutput)
        //###↓
        let frameRate = videoTrack.nominalFrameRate
        let startTime = CMTime(value: CMTimeValue(startPoint), timescale: CMTimeScale(frameRate))
        let timeRange = CMTimeRange(start: startTime, end: .positiveInfinity)
        reader.timeRange = timeRange //読み込む範囲を`timeRange`で指定
        //###↑
        reader.startReading()
    
        stopButton.isEnabled = false
    
        //以下の時間のかかる処理は別スレッドで起動(そうしないと`stopButton.isEnabled = false`が効かない)
        DispatchQueue.global(qos: .background).async {
            //「頭出し」の処理は何も必要がない。
            //次に`copyNextSampleBuffer()`を呼ぶと`timeRange`の`start`で指定した時刻のフレームが読み込まれる
            //...
            //別thread内で「main thread でしかやってはいけない」処理を書くための定型句
            DispatchQueue.main.async {
                //「main thread でしかやってはいけない」処理(UIの更新もそう)はこの中に書く
                //...
                //例えばstopButtonを有効化しても良いタイミングになったら
                self.stopButton.isEnabled = true
                //...
            }
        }
        //非同期に処理を起動したら、同じメソッド内では何もしてはいけない(と思った方が良い)
    
[/code]

とりあえず、公式ドキュメントは[`timeRange`](https://developer.apple.com/documentation/avfoundation/avassetreader/1386400-timerange)やら、[`CMTime.init(value:timescale:)`](https://developer.apple.com/documentation/coremedia/cmtime/1489173-init)やら。"AVAssetReader
seek timeRange"辺りで検索すると(英語だったり、SwiftじゃなくObjective-Cだったりしますが)コード例が見られると思います。

で、(*)なのですが、

  * ビデオのフォーマットがフレームレート固定でない場合
  * (固定だとしても)29.97fpsとかの中途半端な数字の場合

なんかに正確な位置から頭出しをしてくれるとは限りません。

従って、1フレーム単位で正確な頭出しが必要な場合には、(`AVAssertReader`を使うなら)「フレーム数まで空回り」も仕方ないかと思います。ただし`while
reader.status !=
AVAssetReaderStatus.reading`の部分は余計でしょう。[`AVAssertReader`のドキュメント](https://developer.apple.com/documentation/avfoundation/avassetreader/1390211-status)を見ても`copyNextSampleBuffer()`が`nil`を返したときには`status`のチェックをすべきだとしか書いていません。

[code]

        //...
        reader.add(readerOutput)
        reader.startReading()
    
        stopButton.isEnabled = false
    
        //以下の時間のかかる処理は別スレッドで起動(そうしないと`stopButton.isEnabled = false`が効かない)
        DispatchQueue.global(qos: .background).async {
            //どうしてもフレーム単位にしたい時の「頭出し」の処理
            seek: for _ in 0..<startPoint {
                let sample = readerOutput.copyNextSampleBuffer()
                if sample == nil {
                    let status = reader.status
                    switch status {
                    case .cancelled:
                        //キャンセルされた時の処理
                        //...
                        break seek //別スレッドでの処理を終了したいならreturn(以下同様)
                    case .completed:
                        //全部のデータを読み込み終わった時の処理
                        //...
                        break seek
                    case .failed:
                        //何らかのエラー
                        //...
                        break seek
                    case .unknown:
                        //startReading()を呼ぶ前の`status`を参照しない限り普通はあり得ない
                        //...
                        break seek
                    case .reading:
                        //`status`が`reading`で`nil`が返ることはないはずだが...
                        //...
                        continue seek
                    }
                }
            }
            //...
            //別thread内で「main thread でしかやってはいけない」処理を書くための定型句
            DispatchQueue.main.async {
                //...
            }
        }
        //非同期に処理を起動したら、同じメソッド内では何もしてはいけない(と思った方が良い)
    
[/code]

このコード例では`copyNextSampleBuffer()`が`nil`を返した時の処理が延々と書いてありますが、「頭出し」の位置に至るまでに`nil`が返るのは異常系なので、もうちょっとまとめてさらっと書いても良いかもしれません。重要なのは、「`nil`が帰ってこない限り、ひたすら`copyNextSampleBuffer()`を呼び続けるだけで良い」(非`nil`なら`status`のチェックは不要)と言う点です。

どちらかのコード、必要に応じておためしください。


CC BY-SA 4.0
2018-09-18T16:27:38.940
48517
2018-09-18T16:27:38.940
13972
48491
answer