Encodage H. 264 de la caméra avec Android MediaCodec
J'essaie de faire fonctionner cela sur Android 4.1 (en utilisant une tablette ASUS Transformer mise à niveau). Grâce à la réponse de Alex à ma question précédente , j'ai déjà pu écrire des données H. 264 brutes dans un fichier, mais ce fichier n'est jouable qu'avec ffplay -f h264
, et il semble qu'il ait perdu toutes les informations concernant le framerate (lecture extrêmement rapide). En outre, l'espace de couleur semble incorrect (atm en utilisant la valeur par défaut de la caméra sur le côté de l'encodeur).
public class AvcEncoder {
private MediaCodec mediaCodec;
private BufferedOutputStream outputStream;
public AvcEncoder() {
File f = new File(Environment.getExternalStorageDirectory(), "Download/video_encoded.264");
touch (f);
try {
outputStream = new BufferedOutputStream(new FileOutputStream(f));
Log.i("AvcEncoder", "outputStream initialized");
} catch (Exception e){
e.printStackTrace();
}
mediaCodec = MediaCodec.createEncoderByType("video/avc");
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 320, 240);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
}
public void close() {
try {
mediaCodec.stop();
mediaCodec.release();
outputStream.flush();
outputStream.close();
} catch (Exception e){
e.printStackTrace();
}
}
// called from Camera.setPreviewCallbackWithBuffer(...) in other class
public void offerEncoder(byte[] input) {
try {
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
outputStream.write(outData, 0, outData.length);
Log.i("AvcEncoder", outData.length + " bytes written");
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
Changer le type de codeur en "video/mp4" résout apparemment le problème du framerate, mais puisque l'objectif principal est de créer un service de streaming, ce n'est pas une bonne solution.
Je suis conscient que j'ai laissé tomber une partie du code D'Alex en considérant les SPS et PPS NALU, mais j'espérais que cela ne serait pas nécessaire puisque cette information provenait également de outData
et j'ai supposé que l'encodeur formaterait correctement. Si ce n'est pas le cas, comment dois-je organiser les différents types de NALU dans mon fichier/flux?
Alors, que suis-je manquant ici pour faire un flux H. 264 valide et fonctionnel? Et quels paramètres dois-je utiliser pour faire correspondre l'espace colorimétrique de la caméra et l'espace colorimétrique de l'encodeur?
J'ai le sentiment que c'est plus une question liée à H. 264 qu'un sujet Android/MediaCodec. Ou est-ce que je n'utilise toujours pas correctement L'API MediaCodec?
Merci d'avance.
5 réponses
Pour votre problème de lecture rapide, il n'y a rien à faire ici. Comme il s'agit d'une solution de streaming, l'autre côté doit être informé de la fréquence d'images à l'avance ou des horodatages avec chaque image. Ces deux éléments ne font pas partie du flux élémentaire. Soit framerate prédéterminé est choisi ou vous transmettez un sdp ou quelque chose comme ça ou vous utilisez des protocoles existants comme rtsp. Dans le second cas, les horodatages font partie du flux envoyé sous la forme de quelque chose comme rtp. Ensuite le client doit depay le flux rtp et jouer bacl. Voici comment fonctionne le streaming élémentaire. [soit fixer votre fréquence d'images si vous avez un encodeur à taux fixe ou donner des horodatages]
La lecture locale du PC sera rapide car elle ne connaîtra pas le fps. En donnant le paramètre fps avant l'entrée par exemple
ffplay -fps 30 in.264
Vous pouvez contrôler la lecture sur le PC.
Quant au fichier n'étant pas jouable: a-t-il un SPS et un PPS. Vous devriez également avoir des en - têtes naux activés-format de l'annexe B. Je ne sais pas beaucoup sur android, mais c'est une exigence pour tout flux élémentaire H. 264 pour être jouable quand ils ne sont pas dans des conteneurs et doivent être déversés et lus plus tard. Si la valeur par défaut d'android est mp4, mais les en-têtes annexb par défaut seront désactivés, alors peut-être qu'il y a un commutateur pour l'activer. Ou si vous obtenez des données image par image, ajoutez-les vous-même.
En ce qui concerne le format de couleur: je suppose que la valeur par défaut devrait fonctionner. Essayez donc de ne pas le mettre. Sinon, essayez 422 planaire ou UVYV / vyuy entrelacé format. habituellement, les caméras sont l'un de ceux-ci. (mais pas nécessaire, ceux-ci peuvent être ceux que j'ai rencontrés le plus souvent).
Android 4.3 (API 18) fournit une solution facile. La classe MediaCodec
accepte maintenant l'entrée des Surfaces, ce qui signifie que vous pouvez connecter l'aperçu de la surface de la caméra à l'encodeur et contourner tous les problèmes de format YUV étranges.
Il y a aussi une nouvelle classe MediaMuxer qui convertira votre flux H. 264 brut en fichier. mp4 (éventuellement en fusion dans un flux audio).
Voir la source CameraToMpegTest pour un exemple de faire exactement cela. (Il montre également l'utilisation d'un fragment shader OpenGL ES pour effectuer une édition triviale sur la vidéo au fur et à mesure de son enregistrement.)
Vous pouvez convertir des espaces colorimétriques comme celui-ci, si vous avez défini L'espace colorimétrique de prévisualisation sur YV12:
public static byte[] YV12toYUV420PackedSemiPlanar(final byte[] input, final byte[] output, final int width, final int height) {
/*
* COLOR_TI_FormatYUV420PackedSemiPlanar is NV12
* We convert by putting the corresponding U and V bytes together (interleaved).
*/
final int frameSize = width * height;
final int qFrameSize = frameSize/4;
System.arraycopy(input, 0, output, 0, frameSize); // Y
for (int i = 0; i < qFrameSize; i++) {
output[frameSize + i*2] = input[frameSize + i + qFrameSize]; // Cb (U)
output[frameSize + i*2 + 1] = input[frameSize + i]; // Cr (V)
}
return output;
}
Ou
public static byte[] YV12toYUV420Planar(byte[] input, byte[] output, int width, int height) {
/*
* COLOR_FormatYUV420Planar is I420 which is like YV12, but with U and V reversed.
* So we just have to reverse U and V.
*/
final int frameSize = width * height;
final int qFrameSize = frameSize/4;
System.arraycopy(input, 0, output, 0, frameSize); // Y
System.arraycopy(input, frameSize, output, frameSize + qFrameSize, qFrameSize); // Cr (V)
System.arraycopy(input, frameSize + qFrameSize, output, frameSize, qFrameSize); // Cb (U)
return output;
}
Vous pouvez interroger MediaCodec pour son format bitmap pris en charge et interroger votre aperçu. Le problème est que certains MediaCodecs ne prennent en charge que les formats YUV emballés propriétaires que vous ne pouvez pas obtenir à partir de l'aperçu. En particulier 2130706688 = 0x7F000100 = COLOR_TI_FormatYUV420PackedSemiPlanar . Le format par défaut pour l'aperçu est 17 = NV21 = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar = YCbCr 420 Semi Planar
Si vous n'avez pas demandé explicitement un autre format de pixel, les tampons de prévisualisation de la caméra arriveront dans un format YUV 420 appelé NV21 , pour lequel COLOR_FormatYCrYCb est L'équivalent MediaCodec.
Malheureusement, comme le mentionnent d'autres réponses sur cette page, il n'y a aucune garantie que sur votre appareil, L'encodeur AVC supporte ce format. Notez qu'il existe des périphériques étranges qui ne supportent pas NV21, mais je n'en connais pas qui peuvent être mis à niveau vers API 16 (par conséquent, avoir MediaCodec).
La documentation de Google affirme également que YV12 planar YUV doit être pris en charge en tant que format de prévisualisation de la caméra pour tous les appareils avec API >= 12. Par conséquent, il peut être utile de l'essayer (L'équivalent MediaCodec est COLOR_FormatYUV420Planar que vous utilisez dans votre extrait de code).
Update : comme Andrew Cottrell me l'a rappelé, YV12 a encore besoin de Chroma swapping pour devenir COLOR_FormatYUV420Planar.