Como reduzir o atraso de início do iOS AVPlayer

115

Observação, para a pergunta abaixo: Todos os ativos são locais no dispositivo - nenhum streaming de rede está ocorrendo. Os vídeos contêm faixas de áudio.

Estou trabalhando em um aplicativo iOS que requer a reprodução de arquivos de vídeo com atraso mínimo para iniciar o videoclipe em questão. Infelizmente, não sabemos qual será o próximo videoclipe até que realmente precisemos iniciá-lo. Especificamente: Quando um videoclipe está sendo reproduzido, saberemos qual é o próximo conjunto de (aproximadamente) 10 videoclipes, mas não sabemos qual exatamente, até que chegue a hora de reproduzir "imediatamente" o próximo clipe.

O que fiz para ver os atrasos reais de início foi chamar addBoundaryTimeObserverForTimeso player de vídeo, com um período de um milissegundo para ver quando o vídeo realmente começou a ser reproduzido, e considero a diferença desse carimbo de hora com o primeiro lugar em o código que indica qual ativo começar a jogar.

Pelo que vi até agora, descobri que usar a combinação de AVAssetcarregamento e, em seguida, criar um a AVPlayerItempartir disso quando estiver pronto e, em seguida, esperar AVPlayerStatusReadyToPlayantes de chamar o jogo, tende a levar entre 1 e 3 segundos para iniciar o grampo.

Desde então mudei para o que considero mais ou menos equivalente: ligar [AVPlayerItem playerItemWithURL:]e esperar para AVPlayerItemStatusReadyToPlayjogar. Praticamente o mesmo desempenho.

Uma coisa que estou observando é que o carregamento do primeiro item do AVPlayer é mais lento do que o resto. Parece que uma ideia é fazer um pré-voo do AVPlayer com um ativo curto / vazio antes de tentar reproduzir o primeiro vídeo. Isso pode ser uma boa prática geral. [ Início lento para AVAudioPlayer na primeira vez que um som é reproduzido

Eu adoraria diminuir o tempo de início do vídeo o máximo possível e ter algumas ideias de coisas para experimentar, mas gostaria de receber orientação de alguém que possa ajudar.

Atualização: a ideia 7, abaixo, conforme implementada, produz tempos de comutação de cerca de 500 ms. Isso é uma melhoria, mas seria bom fazer isso ainda mais rápido.

Ideia 1: Use N AVPlayers (não funciona)

Usando ~ 10 AVPPlayerobjetos, inicie e pause todos os ~ 10 clipes e, quando soubermos qual deles realmente precisamos, mude para e retome o correto AVPlayere comece tudo de novo para o próximo ciclo.

Não acho que isso funcione, porque li que há um limite de 4 ativos AVPlayer'sno iOS. Alguém perguntou sobre isso no StackOverflow aqui e descobriu sobre o limite de 4 AVPlayer: fast-switching-between-videos-using-avfoundation

Ideia 2: Use AVQueuePlayer (não funciona)

Não acredito que enfiar 10 AVPlayerItemsem um AVQueuePlayerpré-carregue todos eles para um início perfeito. AVQueuePlayeré uma fila e acho que realmente só deixa o próximo vídeo da fila pronto para reprodução imediata. Não sei qual dos cerca de 10 vídeos queremos reproduzir, até que seja hora de começar esse. ios-avplayer-video-preloading

Ideia 3: carregar, reproduzir e manter AVPlayerItemsem segundo plano (ainda não tenho 100% de certeza - mas não parece bom)

Estou verificando se há algum benefício em carregar e reproduzir o primeiro segundo de cada videoclipe em segundo plano (suprimir a saída de vídeo e áudio) e manter uma referência para cada um AVPlayerIteme quando sabemos qual item precisa ser reproduzido real, troque aquele em, e troque o AVPlayer de fundo com o ativo. Enxague e repita.

A teoria seria que os reproduzidos recentemente AVPlayer/AVPlayerItemainda podem conter alguns recursos preparados que tornariam a reprodução subsequente mais rápida. Até agora, não vi benefícios disso, mas posso não ter a AVPlayerLayerconfiguração correta para o plano de fundo. Duvido que isso realmente melhore as coisas pelo que tenho visto.

Ideia 4: Use um formato de arquivo diferente - talvez um que seja mais rápido de carregar?

No momento, estou usando o formato .m4v (vídeo-MPEG4) H.264. O H.264 tem várias opções de codec diferentes, portanto, é possível que algumas opções sejam mais rápidas de procurar do que outras. Descobri que o uso de configurações mais avançadas que tornam o tamanho do arquivo menor aumenta o tempo de busca, mas não encontrei nenhuma opção que vá de outra maneira.

Ideia 5: Combinação de formato de vídeo sem perdas + AVQueuePlayer

Se houver um formato de vídeo que carregue rapidamente, mas talvez o tamanho do arquivo seja insano, uma ideia pode ser preparar os primeiros 10 segundos de cada clipe de vídeo com uma versão que está inchada, mas mais rápida de carregar, mas de volta isso com um recurso codificado em H.264. Use um AVQueuePlayer e adicione os primeiros 10 segundos no formato de arquivo descompactado e, em seguida, use um que esteja em H.264, que obtém até 10 segundos de preparação / pré-carregamento. Portanto, eu obteria 'o melhor' dos dois mundos: tempos de início rápidos, mas também se beneficia de um formato mais compacto.

Ideia 6: Usar um AVPlayer não padrão / escrever meu / usar o de outra pessoa

Dadas as minhas necessidades, talvez eu não possa usar o AVPlayer, mas tenho que recorrer a AVAssetReader e decodificar os primeiros segundos (possivelmente gravar o arquivo bruto no disco) e, quando se trata de reprodução, fazer uso do formato bruto para reproduzi-lo de volta rápido. Parece um grande projeto para mim, e se eu fizer isso de uma forma ingênua, é pouco claro / improvável que funcione melhor. Cada quadro de vídeo decodificado e descompactado tem 2,25 MB. Falando ingenuamente - se usarmos ~ 30 fps para o vídeo, eu acabaria com o requisito de leitura do disco de ~ 60 MB / s, o que provavelmente é impossível / forçando-o. Obviamente, teríamos que fazer algum nível de compressão de imagem (talvez formatos de compressão openGL / es nativos via PVRTC) ... mas isso é meio louco. Talvez haja uma biblioteca que eu possa usar?

Ideia 7: Combine tudo em um único recurso de filme e procureToTime

Uma ideia que pode ser mais fácil do que algumas das anteriores, é combinar tudo em um único filme e usar seekToTime. O fato é que estaríamos pulando por todo o lugar. Acesso essencialmente aleatório ao filme. Acho que isso pode realmente funcionar bem: avplayer-movie-playing-lag-in-ios5

Qual abordagem você acha que seria a melhor? Até agora, não fiz muito progresso em termos de redução do atraso.

Bernt Habermeier
fonte
Pelo que vale a pena, vou com a Idéia 7. Ainda é lento, mas não tão imprevisivelmente lento quanto as outras opções. A próxima pergunta que tenho é - as opções de codec, resolução e frequência de quadro-chave têm impacto sobre o tempo de busca?
Bernt Habermeier
Atrasado para o jogo, mas pode valer a pena trocar os vídeos o mais rápido possível (ou seja, imediatamente após o novo começar a ser reproduzido) e criar o perfil do aplicativo para ver onde ele passa a maior parte do tempo de CPU.
tc.
1
Quase um ano depois, o que você descobriu com isso?
lnafziger de
1
Fui com a opção 7, e cheguei a um lugar entre 300ms e 500ms de buscas. Uma coisa que descobri é que quanto mais sofisticadas as opções de codec mp4, mais lento é o searchTo. Existem algumas opções de compactação de vídeo que permitem uma melhor compactação e mantêm a qualidade do vídeo, mas matam o tempo de decodificação.
Bernt Habermeier
2
Essa é uma solicitação que parece razoável, mas para realmente implementar a opção 7, existem muitas facetas na implementação para postar. Considere: (a) Faça uma cadeia de ferramentas para mesclar ativos de vídeo, (b) certifique-se de acompanhar os deslocamentos do segmento de vídeo, (c) os deslocamentos de pesquisa quando uma solicitação chega para reproduzir um clipe específico, (d) use addPeriodicTimeObserverForInterval para verificar se você executou um videoclipe e reagiu de acordo (use este método em vez de um addBoundaryTimeObserverForTimes de disparo único porque descobri que o último às vezes não é acionado ... no geral, isso não serve para colar o código.
Bernt Habermeier

Respostas:

4

Para iOS 10.xe superior, para reduzir o atraso no início do AVPlayer, eu defini: avplayer.automaticallyWaitsToMinimizeStalling = false; e isso pareceu consertar para mim. Isso pode ter outras consequências, mas ainda não as atingi.

Tive a ideia em: https://stackoverflow.com/a/50598525/9620547

grizzb
fonte
Eu tenho 6-7 segundos de redução no atraso. Mas eu tenho uma pergunta aqui, isso afetará o desempenho do aplicativo?
Kalpa
@kalpa Usamos esse código em um aplicativo de produção por mais de um ano sem quaisquer efeitos negativos perceptíveis. Reproduzimos principalmente arquivos de áudio de 20 a 60 minutos para ouvintes nos EUA, onde a cobertura de dados móveis é geralmente rápida. Porém, seu caso de uso pode ser diferente.
grizzb
Obrigado pela informação. @grizzb
kalpa
1

O ativo pode não estar pronto depois de criá-lo, ele pode fazer cálculos como a duração do filme, certifique-se de conter todos os metadados do filme no arquivo.

nova
fonte
1

Você deve tentar a opção nº 7 primeiro, apenas para ver se consegue fazê-la funcionar. Suspeito que não funcionará de fato para as suas necessidades, já que o tempo de busca provavelmente não será rápido o suficiente para permitir uma alternância perfeita entre os clipes. Se você tentar isso e falhar, eu aconselho que você escolha a opção 4/6 e dê uma olhada em minha biblioteca iOS projetada especificamente para esse propósito, basta fazer uma rápida pesquisa no Google sobre AVAnimator para saber mais. Minha biblioteca torna possível implementar loops contínuos e mudar de um clipe para outro, é muito rápido porque o vídeo tem que ser decodificado em um arquivo antes da mão. No seu caso, todos os 10 videoclipes seriam decodificados em arquivos antes de você começar, mas a troca entre eles seria rápida.

MoDJ
fonte
E quanto à parte de áudio do vídeo? Preciso que o vídeo e o áudio estejam sincronizados.
Bernt Habermeier
Sim, o áudio já é tratado com uma sincronização muito estreita entre a trilha de áudio e o videoclipe. Veja os exemplos de projetos xcode. Já está tudo implementado, basta fazer o download e experimentar.
MoDJ
Existe a possibilidade de reproduzir vídeo em rede usando AVAnimator?
Richard Topchii
Não, funciona em arquivos locais, streaming de vídeo em rede é uma coisa completamente diferente.
MoDJ de
0

Sem ter feito nada parecido no passado, com base em seus pensamentos e experiências, eu tentaria uma combinação de 7 e 1: Pré-carregar um AVPlayer com os primeiros segundos dos 10 vídeos de acompanhamento. Então, pular provavelmente será mais rápido e confiável devido a menos dados. Enquanto reproduz a peça selecionada, você tem tempo suficiente para preparar o AVPlayer para o restante do vídeo de acompanhamento selecionado em segundo plano. Quando o início estiver concluído, você alterna para o AVPlayer preparado. Portanto, no total, a qualquer momento, você terá no máximo 2 AVPlayers carregados.

É claro que não sei se a mudança pode ser feita de forma tão suave que não perturbe a reprodução.

(Teria adicionado como comentário se pudesse.)

Atenciosamente, Peter

ilmíacos
fonte
Não encontrei nenhum benefício em carregar 10 ativos em série em um AVPlayer. Além disso, não entendo sua sugestão, pois vejo as opções (1) e (7) mutuamente exclusivas. A opção 7 mescla todos os ativos de vídeo em um único ativo - portanto, há apenas um único ativo para carregar o período. É isso que estou fazendo hoje e, pelo que vale a pena, estou obtendo um atraso de aproximadamente 500 ms nos tempos reais de início / reprodução. É importante notar que o SeekTo é concluído mais rápido do que o primeiro quadro real é reproduzido, portanto, para tempos de início reais, eu meço quando o primeiro quadro está realmente sendo reproduzido por meio de retorno de chamada cronometrado.
Bernt Habermeier
Só para deixar minha ideia clara: minha ideia era dividir um ativo em duas partes: os primeiros segundos e o resto. Agora você fez dois ativos de um. Os 10 começos que você junta e usa pula e enquanto joga o começo você carrega o resto. Isso inclui a sugestão 8, que adiciona à sua lista.
ilmiacs
Mas, pelo que entendi com seu último comentário, enquanto isso, você avançou na pesquisa, o que é bom, e a solução 7 também não funcionou. Parece que o AV fundamentalmente fica no seu caminho e a única maneira de você ir provavelmente é usar a tecnologia subjacente, ou seja, Core Media, para obter mais controle sobre seus ativos. Peter.
ilmiacs
Oh, eu entendo melhor sua ideia agora. Obrigado. Seria interessante investigar se você obtém tempos de busca rápidos para videoclipes curtos. Ainda não tentei, mas vale a pena considerar. Com relação ao uso da mídia principal - não encontrei nenhum bom material de referência sobre essa API. Você tem um bom recurso para me apontar?
Bernt Habermeier
Não desculpe. Como eu disse, não sou um especialista em AV ou Core Media. Acabei de ler sua pergunta e tive algumas idéias de como eu procederia pessoalmente e gostaria de compartilhá-las. Peter
ilmiacs
0

Se entendi seu problema corretamente, parece que você tem um vídeo contínuo para o qual precisa carregar a faixa de áudio imediatamente.

Se for esse o caso, sugiro olhar para o BASS . BASS é uma biblioteca de áudio muito parecida com o AVPlayer, que fornece acesso (relativamente) fácil às APIs de baixo nível da estrutura AudioUnits no iOS. O que isso significa para você? Isso significa que, com um pouquinho de manipulação do buffer (você pode nem precisar, depende de quão minúsculo você deseja o atraso), você pode começar a reproduzir música instantaneamente.

As limitações no entanto se estendem ao vídeo, como eu disse, é uma biblioteca de áudio então qualquer manipulação de vídeo ainda terá que ser feita com o AVPlayer. No entanto usando-seekToTime:toleranfeBefore:toleranceAfter: o você deve conseguir uma busca rápida no vídeo, contanto que faça a pré-rolagem com todas as opções necessárias.

Se você estiver sincronizando em vários dispositivos (o que seu aplicativo pode sugerir), deixe um comentário e será um prazer editar minha resposta.

PS: BASS pode parecer assustador no início por causa de seu formato semelhante ao C, mas é realmente muito fácil de usar pelo que é.

asno
fonte
-2

Aqui estão várias propriedades e métodos fornecidos pela classe AVAsset que podem ajudar:

- (void)_pu_setCachedDuration:(id)arg1;
- (id)pu_cachedDuration; 
- (struct
 { 
   long long x1; 
   int x2;
   unsigned int x3; 
   long long x4; 
})pu_duration;
- (void)pu_loadDurationWithCompletionHandler:(id /* block */)arg1;
James Bush
fonte