2016-08-22 16 views
14

Nowa EdycjaTylko pierwszy utwór Odtwarzanie z AVMutableComposition()

już odwoływać

AVMutableComposition - Only Playing First Track (Swift)

ale nie dostarcza odpowiedzi na to, co szukam.

Mam AVMutableComposition(). Próbuję zastosować wiele AVCompositionTrack, z jednego rodzaju AVMediaTypeVideo w tej jednej kompozycji. To dlatego, że używam 2 różne AVMediaTypeVideo źródeł z różnymi CGSize „S i preferredTransforms z AVAsset” s one pochodzą.

więc jedynym sposobem, aby zastosować swoją określony preferredTransforms jest dostarczenie ich w 2 różnych torach. Ale z jakiegoś powodu tylko pierwszy utwór faktycznie dostarczy dowolny film, prawie tak, jakby drugi utwór nigdy nie był dostępny.

Więc próbowałem

1) za pomocą AVMutableVideoCompositionLayerInstruction „s i stosowaniu AVVideoComposition wraz z AVAssetExportSession, która działa w porządku, jestem nadal pracuje na transformacji, ale jest do-stanie. Ale czas przetwarzania wideo jest DOBRZE PONAD 1 minutę, co nie ma zastosowania w mojej sytuacji.

2) Korzystanie z wielu ścieżek, bez AVAssetExportSession i 2. torze tego samego typu nie pojawi. Teraz mogę umieścić wszystko na 1 ścieżce, ale wszystkie filmy będą miały ten sam rozmiar i preferredTransform jak pierwsze wideo, którego absolutnie nie chcę, ponieważ rozciąga je na wszystkie strony.

Więc moje pytanie brzmi, czy jest możliwe

1) instrukcje Stosując tylko tor bez użycia AVAssetExportSession? // Preferowany sposób BY FAR.

2) Zmniejszenie czasu eksportu? (Próbowałem użyć PresetPassthrough, ale nie możesz tego użyć, jeśli masz exporter.videoComposition, gdzie są moje instrukcje.) To jedyne miejsce, w którym mogę umieścić instrukcje, nie wiem, czy mogę je umieścić gdzie indziej. to niektóre z moich kodu (bez eksportera, jak nie trzeba nigdzie eksportować czegokolwiek, po prostu robić rzeczy po AVMutableComposition łączy elementy.

func merge() { 
    if let firstAsset = controller.firstAsset, secondAsset = self.asset { 

     let mixComposition = AVMutableComposition() 

     let firstTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, 
                    preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) 
     do { 
      //Don't need now according to not being able to edit first 14seconds. 

      if(CMTimeGetSeconds(startTime) == 0) { 
       self.startTime = CMTime(seconds: 1/600, preferredTimescale: Int32(600)) 
      } 
      try firstTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, CMTime(seconds: CMTimeGetSeconds(startTime), preferredTimescale: 600)), 
              ofTrack: firstAsset.tracksWithMediaType(AVMediaTypeVideo)[0], 
              atTime: kCMTimeZero) 
     } catch _ { 
      print("Failed to load first track") 
     } 


     //This secondTrack never appears, doesn't matter what is inside of here, like it is blank space in the video from startTime to endTime (rangeTime of secondTrack) 
     let secondTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, 
                    preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) 
//   secondTrack.preferredTransform = self.asset.preferredTransform 
     do { 
      try secondTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, secondAsset.duration), 
              ofTrack: secondAsset.tracksWithMediaType(AVMediaTypeVideo)[0], 
              atTime: CMTime(seconds: CMTimeGetSeconds(startTime), preferredTimescale: 600)) 
     } catch _ { 
      print("Failed to load second track") 
     } 

     //This part appears again, at endTime which is right after the 2nd track is suppose to end. 
     do { 
      try firstTrack.insertTimeRange(CMTimeRangeMake(CMTime(seconds: CMTimeGetSeconds(endTime), preferredTimescale: 600), firstAsset.duration-endTime), 
              ofTrack: firstAsset.tracksWithMediaType(AVMediaTypeVideo)[0] , 
              atTime: CMTime(seconds: CMTimeGetSeconds(endTime), preferredTimescale: 600)) 
     } catch _ { 
      print("failed") 
     } 
     if let loadedAudioAsset = controller.audioAsset { 
      let audioTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: 0) 
      do { 
       try audioTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, firstAsset.duration), 
               ofTrack: loadedAudioAsset.tracksWithMediaType(AVMediaTypeAudio)[0] , 
               atTime: kCMTimeZero) 
      } catch _ { 
       print("Failed to load Audio track") 
      } 
     } 
    } 
} 

Edit

Jabłko stwierdza, że ​​„Wskazuje instrukcje do kompozycji wideo poprzez NSArray wystąpień klas implementujących protokół AVVideoCompositionInstruction. W przypadku pierwszej instrukcji w tablicy timeRange.start musi być mniejszy lub równy najwcześniejszemu czasowi, dla którego zostanie wykonane odtwarzanie lub inne przetwarzanie (należy pamiętać, że zwykle będzie to kCMTimeZero). W przypadku kolejnych instrukcji timeRange.start musi być równy czasowi zakończenia poprzedniej instrukcji. Czas zakończenia ostatnia instrukcja musi być większa lub równa najnowszemu czasowi, dla którego będzie podejmowane odtwarzanie lub inne przetwarzanie (należy pamiętać, że często będzie to czas trwania zasobu, z którym powiązana jest instancja AVVideoComposition)."

To tylko stwierdza, że ​​cała kompozycja musi być warstwowa wewnątrz instrukcji, jeśli zdecydujesz się użyć DOWOLNE instrukcje (to jest to, co rozumiem). Dlaczego tak jest? Jak po prostu zastosować instrukcje, aby powiedzieć ścieżkę 2 na tym przykładzie bez uwzględnienia zmieniających się utwór 1 lub 3 w ogóle.

tor 1 z 0 - 10s, track 2 z 10 - 20sekund, track 3 z 20 - 30sek

Wszelkie wyjaśnienia na które prawdopodobnie odpowiedzieć na moje pytanie (jeśli jest to wykonalne)

+0

Kiedy mówisz, że drugi utwór nigdy tam nie jest, czy masz na myśli, że widzisz tło kompozycji, czy też paliback zatrzymuje się tuż po pierwszym utworze? –

+0

Mam na myśli, że pierwszy utwór jest odtwarzany, przechodzi w tryb BLANK, a po ukończeniu drugiego utworu wraca do pierwszego utworu – impression7vx

+0

Jaką transformację stosuje się do drugiego utworu? Może znajduje się poza ramką wideoComposition. –

Odpowiedz

1

Ok, więc dla mojego dokładnego problemu, musiałem zastosować specyficzne transformacje CGAffineTransform w Swift, aby uzyskać konkretne wyniki, które chcieliśmy. Aktualna jestem delegowania działa z każdym Zdjęcie zrobione/otrzymany jak również wideo

//This method gets the orientation of the current transform. This method is used below to determine the orientation 
func orientationFromTransform(_ transform: CGAffineTransform) -> (orientation: UIImageOrientation, isPortrait: Bool) { 
    var assetOrientation = UIImageOrientation.up 
    var isPortrait = false 
    if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 { 
     assetOrientation = .right 
     isPortrait = true 
    } else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 { 
     assetOrientation = .left 
     isPortrait = true 
    } else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 { 
     assetOrientation = .up 
    } else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 { 
     assetOrientation = .down 
    } 

    //Returns the orientation as a variable 
    return (assetOrientation, isPortrait) 
} 

//Method that lays out the instructions for each track I am editing and does the transformation on each individual track to get it lined up properly 
func videoCompositionInstructionForTrack(_ track: AVCompositionTrack, _ asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction { 

    //This method Returns set of instructions from the initial track 

    //Create inital instruction 
    let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track) 

    //This is whatever asset you are about to apply instructions to. 
    let assetTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0] 

    //Get the original transform of the asset 
    var transform = assetTrack.preferredTransform 

    //Get the orientation of the asset and determine if it is in portrait or landscape - I forget which, but either if you take a picture or get in the camera roll it is ALWAYS determined as landscape at first, I don't recall which one. This method accounts for it. 
    let assetInfo = orientationFromTransform(transform) 

    //You need a little background to understand this part. 
    /* MyAsset is my original video. I need to combine a lot of other segments, according to the user, into this original video. So I have to make all the other videos fit this size. 
     This is the width and height ratios from the original video divided by the new asset 
    */ 
    let width = MyAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.width/assetTrack.naturalSize.width 
    var height = MyAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.height 

    //If it is in portrait 
    if assetInfo.isPortrait { 

     //We actually change the height variable to divide by the width of the old asset instead of the height. This is because of the flip since we determined it is portrait and not landscape. 
     height = MyAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.width 

     //We apply the transform and scale the image appropriately. 
     transform = transform.scaledBy(x: height, y: height) 

     //We also have to move the image or video appropriately. Since we scaled it, it could be wayy off on the side, outside the bounds of the viewing. 
     let movement = ((1/height)*assetTrack.naturalSize.height)-assetTrack.naturalSize.height 

     //This lines it up dead center on the left side of the screen perfectly. Now we want to center it. 
     transform = transform.translatedBy(x: 0, y: movement) 

     //This calculates how much black there is. Cut it in half and there you go! 
     let totalBlackDistance = MyAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.width-transform.tx 
     transform = transform.translatedBy(x: 0, y: -(totalBlackDistance/2)*(1/height)) 

    } else { 

     //Landscape! We don't need to change the variables, it is all defaulted that way (iOS prefers landscape items), so we scale it appropriately. 
     transform = transform.scaledBy(x: width, y: height) 

     //This is a little complicated haha. So because it is in landscape, the asset fits the height correctly, for me anyway; It was just extra long. Think of this as a ratio. I forgot exactly how I thought this through, but the end product looked like: Answer = ((Original height/current asset height)*(current asset width))/(Original width) 
     let scale:CGFloat = ((MyAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.height)*(assetTrack.naturalSize.width))/MyAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.width 
     transform = transform.scaledBy(x: scale, y: 1) 

     //The asset can be way off the screen again, so we have to move it back. This time we can have it dead center in the middle, because it wasn't backwards because it wasn't flipped because it was landscape. Again, another long complicated algorithm I derived. 
     let movement = ((MyAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.width-((MyAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.height)*(assetTrack.naturalSize.width)))/2)*(1/MyAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize.height/assetTrack.naturalSize.height) 
     transform = transform.translatedBy(x: movement, y: 0) 
    } 

    //This creates the instruction and returns it so we can apply it to each individual track. 
    instruction.setTransform(transform, at: kCMTimeZero) 
    return instruction 
} 

Teraz, gdy mamy tych metod, możemy teraz zastosować właściwe i odpowiednie transformacje do naszych aktywów odpowiednio i dostać wszystko montażu miły i czysty.

func merge() { 
if let firstAsset = MyAsset, let newAsset = newAsset { 

     //This creates our overall composition, our new video framework 
     let mixComposition = AVMutableComposition() 

     //One by one you create tracks (could use loop, but I just had 3 cases) 
     let firstTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo, 
                    preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) 

     //You have to use a try, so need a do 
     do { 

      //Inserting a timerange into a track. I already calculated my time, I call it startTime. This is where you would put your time. The preferredTimeScale doesn't have to be 600000 haha, I was playing with those numbers. It just allows precision. At is not where it begins within this individual track, but where it starts as a whole. As you notice below my At times are different You also need to give it which track 
      try firstTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, CMTime(seconds: CMTimeGetSeconds(startTime), preferredTimescale: 600000)), 
              of: firstAsset.tracks(withMediaType: AVMediaTypeVideo)[0], 
              at: kCMTimeZero) 
     } catch _ { 
      print("Failed to load first track") 
     } 

     //Create the 2nd track 
     let secondTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo, 
                     preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) 

     do { 

      //Apply the 2nd timeRange you have. Also apply the correct track you want 
      try secondTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, self.endTime-self.startTime), 
              of: newAsset.tracks(withMediaType: AVMediaTypeVideo)[0], 
              at: CMTime(seconds: CMTimeGetSeconds(startTime), preferredTimescale: 600000)) 
      secondTrack.preferredTransform = newAsset.preferredTransform 
     } catch _ { 
      print("Failed to load second track") 
     } 

     //We are not sure we are going to use the third track in my case, because they can edit to the end of the original video, causing us not to use a third track. But if we do, it is the same as the others! 
     var thirdTrack:AVMutableCompositionTrack! 
     if(self.endTime != controller.realDuration) { 
      thirdTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo, 
                     preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) 

     //This part appears again, at endTime which is right after the 2nd track is suppose to end. 
      do { 
       try thirdTrack.insertTimeRange(CMTimeRangeMake(CMTime(seconds: CMTimeGetSeconds(endTime), preferredTimescale: 600000), self.controller.realDuration-endTime), 
              of: firstAsset.tracks(withMediaType: AVMediaTypeVideo)[0] , 
              at: CMTime(seconds: CMTimeGetSeconds(endTime), preferredTimescale: 600000)) 
      } catch _ { 
       print("failed") 
      } 
     } 

     //Same thing with audio! 
     if let loadedAudioAsset = controller.audioAsset { 
      let audioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: 0) 
      do { 
       try audioTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, self.controller.realDuration), 
               of: loadedAudioAsset.tracks(withMediaType: AVMediaTypeAudio)[0] , 
               at: kCMTimeZero) 
      } catch _ { 
       print("Failed to load Audio track") 
      } 
     } 

     //So, now that we have all of these tracks we need to apply those instructions! If we don't, then they could be different sizes. Say my newAsset is 720x1080 and MyAsset is 1440x900 (These are just examples haha), then it would look a tad funky and possibly not show our new asset at all. 
     let mainInstruction = AVMutableVideoCompositionInstruction() 

     //Make sure the overall time range matches that of the individual tracks, if not, it could cause errors. 
     mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.controller.realDuration) 

     //For each track we made, we need an instruction. Could set loop or do individually as such. 
     let firstInstruction = videoCompositionInstructionForTrack(firstTrack, firstAsset) 
     //You know, not 100% why this is here. This is 1 thing I did not look into well enough or understand enough to describe to you. 
     firstInstruction.setOpacity(0.0, at: startTime) 

     //Next Instruction 
     let secondInstruction = videoCompositionInstructionForTrack(secondTrack, self.asset) 

     //Again, not sure we need 3rd one, but if we do. 
     var thirdInstruction:AVMutableVideoCompositionLayerInstruction! 
     if(self.endTime != self.controller.realDuration) { 
      secondInstruction.setOpacity(0.0, at: endTime) 
      thirdInstruction = videoCompositionInstructionForTrack(thirdTrack, firstAsset) 
     } 

     //Okay, now that we have all these instructions, we tie them into the main instruction we created above. 
     mainInstruction.layerInstructions = [firstInstruction, secondInstruction] 
     if(self.endTime != self.controller.realDuration) { 
      mainInstruction.layerInstructions += [thirdInstruction] 
     } 

     //We create a video framework now, slightly different than the one above. 
     let mainComposition = AVMutableVideoComposition() 

     //We apply these instructions to the framework 
     mainComposition.instructions = [mainInstruction] 

     //How long are our frames, you can change this as necessary 
     mainComposition.frameDuration = CMTimeMake(1, 30) 

     //This is your render size of the video. 720p, 1080p etc. You set it! 
     mainComposition.renderSize = firstAsset.tracks(withMediaType: AVMediaTypeVideo)[0].naturalSize 

     //We create an export session (you can't use PresetPassthrough because we are manipulating the transforms of the videos and the quality, so I just set it to highest) 
     guard let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else { return } 

     //Provide type of file, provide the url location you want exported to (I don't have mine posted in this example). 
     exporter.outputFileType = AVFileTypeMPEG4 
     exporter.outputURL = url 

     //Then we tell the exporter to export the video according to our video framework, and it does the work! 
     exporter.videoComposition = mainComposition 

     //Asynchronous methods FTW! 
     exporter.exportAsynchronously(completionHandler: { 
      //Do whatever when it finishes! 
     }) 
    } 
} 

Wiele się tu dzieje, ale i tak musi być zrobione, na mój przykład! Niestety, wysłanie postu zajęło Ci tyle czasu i daj mi znać, jeśli masz pytania.

1

Tak, możesz całkowicie zastosować pojedynczą transformację do każdej warstwy AVMuta bleComposition.

Oto przegląd procesu - zrobiłem to osobiście w Objective-C, więc nie mogę podać dokładnego kodu, ale wiem, że te same funkcje działają tak samo w Swift.

  1. Utwórz AVMutableComposition.
  2. Utwórz AVMutableVideoComposition.
  3. Umożliwia ustawienie rozmiaru renderowania i czasu trwania klatek filmu wideo.
  4. teraz za każdym AVAsset:
    • Załóż AVAssetTrack oraz AVAudioTrack.
    • Utwórz AVMutableCompositionTrack dla każdej z nich (jedna dla wideo, druga dla audio), dodając każdą do mutableComposition.

tutaj robi się bardziej skomplikowane .. (przepraszam AVFoundation nie jest łatwe!)

  1. Załóż AVMutableCompositionLayerInstruction z AVAssetTrack który odnosi się do każdego filmu. Dla każdej AVMutableCompositionLayerInstruction można ustawić na niej transformację. Można również wykonywać czynności, takie jak ustawianie prostokąta kadrowania.
  2. Dodaj każdą AVMutableCompositionLayerInstruction do tablicy parametrów warstw. Po utworzeniu wszystkich AVMutableCompositionLayerInstructions, tablica zostanie ustawiona na AVMutableVideoComposition.

I wreszcie ..

  1. I wreszcie będziesz miał AVPlayerItem, który będzie używany w tej tyłu (na zasadzie AVPlayer). Tworzysz AVPlayerItem używając AVMutableComposition, a następnie ustawiasz AVMutableVideoComposition na samym AVPlayerItem (setVideoComposition ..)

Easy eh?

Zajęło mi kilka tygodni, aby te rzeczy działały dobrze. Jest całkowicie bezlitosny i jak już wspomniałeś, jeśli zrobisz coś złego, nie powie Ci, co zrobiłeś źle - po prostu się nie pojawia.

Ale kiedy go złamiesz, działa całkowicie szybko i dobrze.

Wreszcie, wszystkie elementy, które przedstawiłem, są dostępne w dokumentach AVFoundation. Jest to długi tom, ale musisz go znać, aby osiągnąć to, co próbujesz zrobić.

Powodzenia!

+0

Doceniam twoją pomoc, już znalazłem odpowiedź. Po prostu tego nie opublikowałem. Dziękuję ci! – impression7vx

+0

@ impression7vx Jakikolwiek postęp w tym przypadku przez przypadek? Coś, co pomoże społeczności? Uderz w blokadę drogi i nie znalazłeś dobrej odpowiedzi. Dzięki! – simplexity

+0

Tak, człowieku. Wczoraj miałem operację, więc daj mi trochę czasu, żeby wrócić do domu i opublikować kod dzisiaj lub jutro. Chłodny? – impression7vx

Powiązane problemy