web-dev-qa-db-fra.com

Créer un fichier vidéo sur Android

Dans mon application Android, j'essaie de créer un fichier vidéo en ajoutant une piste audio à une position temporelle donnée sur la vidéo .J'ai utilisé un MediaMuxer et modifié la valeur de presentationTimeUs pour décaler l'audio . Mais. apparemment ce n’est pas la voie à suivre, car l’heure de début de la vidéo est également décalée . Un autre problème est que l’audio mp3 ne fonctionne pas . Voici ma tentative jusqu’à présent:

final long audioPositionUs = 10000000;
File fileOut = new File (Environment.getExternalStoragePublicDirectory (
  Environment.DIRECTORY_MOVIES) + "/output.mp4");
fileOut.createNewFile ();
MediaExtractor videoExtractor = new MediaExtractor ();
MediaExtractor audioExtractor = new MediaExtractor ();
AssetFileDescriptor videoDescriptor = getAssets ().openFd ("video.mp4");
// AssetFileDescriptor audioDescriptor = getAssets ().openFd ("audio.mp3"); // ?!
AssetFileDescriptor audioDescriptor = getAssets ().openFd ("audio.aac");
videoExtractor.setDataSource (videoDescriptor.getFileDescriptor (),
    videoDescriptor.getStartOffset (), videoDescriptor.getLength ());
audioExtractor.setDataSource (audioDescriptor.getFileDescriptor (),
    audioDescriptor.getStartOffset (), audioDescriptor.getLength ());
MediaFormat videoFormat = null;
for (int i = 0; i < videoExtractor.getTrackCount (); i++) {
  if (videoExtractor.getTrackFormat (i).getString (
      MediaFormat.KEY_MIME).startsWith ("video/")) {
    videoExtractor.selectTrack (i);
    videoFormat = videoExtractor.getTrackFormat (i);
    break;
  }
}
audioExtractor.selectTrack (0);
MediaFormat audioFormat = audioExtractor.getTrackFormat (0);
MediaMuxer muxer = new MediaMuxer (fileOut.getAbsolutePath (),
    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
int videoTrack = muxer.addTrack (videoFormat);
int audioTrack = muxer.addTrack (audioFormat);
boolean end = false;
int sampleSize = 256 * 1024;
ByteBuffer videoBuffer = ByteBuffer.allocate (sampleSize);
ByteBuffer audioBuffer = ByteBuffer.allocate (sampleSize);
MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo ();
MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo ();
videoExtractor.seekTo (0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
audioExtractor.seekTo (0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
muxer.start ();
while (!end) {
  videoBufferInfo.size = videoExtractor.readSampleData (videoBuffer, 0);
  if (videoBufferInfo.size < 0) {
    end = true;
    videoBufferInfo.size = 0;
  } else {
    videoBufferInfo.presentationTimeUs = videoExtractor.getSampleTime ();
    videoBufferInfo.flags = videoExtractor.getSampleFlags ();
    muxer.writeSampleData (videoTrack, videoBuffer, videoBufferInfo);
    videoExtractor.advance ();
  }
}
end = false;
while (!end) {
  audioBufferInfo.size = audioExtractor.readSampleData (audioBuffer, 0);
  if (audioBufferInfo.size < 0) {
    end = true;
    audioBufferInfo.size = 0;
  } else {
    audioBufferInfo.presentationTimeUs = audioExtractor.getSampleTime () +
        audioPositionUs;
    audioBufferInfo.flags = audioExtractor.getSampleFlags ();
    muxer.writeSampleData (audioTrack, audioBuffer, audioBufferInfo);
    audioExtractor.advance ();
  }
}
muxer.stop ();
muxer.release ();

Pouvez-vous s'il vous plaît donner des détails (et le code si possible) pour m'aider à résoudre ce problème?

7
Patrick

Envoyez les échantillons AudioRecord à un wrapper MediaCodec + MediaMuxer. L’utilisation de l’heure système dans audioRecord.read (...) fonctionne suffisamment bien comme un horodatage audio, à condition que vous interrogiez assez souvent pour éviter de remplir le tampon interne d’AudioRecord (pour éviter toute dérive entre le moment où vous appelez en lecture et celui enregistré par AudioRecord). ). Dommage que AudioRecord ne communique pas directement les horodatages ...

// Configuration AudioRecord

while (isRecording) {
    audioPresentationTimeNs = System.nanoTime();
    audioRecord.read(dataBuffer, 0, samplesPerFrame);
    hwEncoder.offerAudioEncoder(dataBuffer.clone(), audioPresentationTimeNs);
}

Notez que AudioRecord garantit uniquement la prise en charge des échantillons PCM 16 bits, bien que MediaCodec.queueInputBuffer prenne les données sous forme d'octet []. Passer un octet [] à audioRecord.read (dataBuffer, ...) tronque les échantillons 16 bits en 8 bits pour vous.

J'ai trouvé que les interrogations de cette façon généraient encore occasionnellement une erreur timestampUs XXX <lastTimestampUs XXX pour piste audio, aussi ai-je inclus une logique permettant de garder trace des bufferInfo.presentationTimeUs signalés par mediaCodec.dequeueOutputBuffer (bufferInfo, timeoutMs) et d'ajuster si nécessaire avant d'appeler mediaMuxer.writeSampleData (trackIndex, encodedData, bufferInfo).

1
umer farooq

En guise de solution de contournement, vous pouvez créer une piste audio temporaire en ajoutant à votre tête une piste silencieuse, puis en l’utilisant avec addTrack.

PS: J'aurais pensé que presentationTimeUs devrait également fonctionner.

PS2: peut-être que la méthode set de MediaCodec.BufferInfo peut aider.

0
sancho.s