2010-02-18 12 views
16

Je veux écrire un encodeur avec ffmpeg qui peut mettre des iFrames (keyframes) aux positions que je veux. Où puis-je trouver des tutoriels ou du matériel de référence pour cela? Est-il possible de le faire avec mencoder ou n'importe quel codeur Open Source. Je veux encoder le fichier H263. J'écris sous & pour linux.Comment écrire un encodeur vidéo avec ffmpeg qui contrôle explicitement la position des images-clés?

+1

..Quelle langue? – Shoban

+0

Je peux écrire en C, C++ mais le tutoriel ou la référence peut être dans n'importe quelle langue. Je veux juste savoir/comprendre l'API ffmpeg. – SunnyShah

Répondre

18

Vous devez consulter la documentation de libavcodec - plus précisément, avcodec_encode_video(). J'ai trouvé que la meilleure documentation disponible se trouve dans les fichiers d'en-tête ffmpeg et le code source d'API fourni avec la source ffmpeg. Plus précisément, regardez libavcodec/api-example.c ou même ffmpeg.c.

Pour forcer une image I, vous devez définir le membre pict_type de l'image que vous codez sur 1: 1 est une image I, 2 est une image P et je ne me souviens plus du code pour un cadre B du haut de ma tête ... En outre, le membre key_frame doit être mis à 1.

Certains documents d'introduction sont disponibles here et here, mais je ne sais pas vraiment comment c'est bon.

Vous devez faire attention à la manière dont vous allouez les objets d'image dont les appels API ont besoin. api-example.c est votre meilleur pari dans la mesure où cela va, à mon avis. Recherchez la fonction video_encode_example() - elle est concise et illustre toutes les choses importantes dont vous devez vous préoccuper - accordez une attention particulière au second appel à avcodec_encode_video() qui passe un argument d'image NULL - il est nécessaire d'obtenir les dernières images de la vidéo La vidéo MPEG est codée hors séquence et vous pouvez vous retrouver avec un retard de quelques images.

+2

Btw, les valeurs pour le membre 'pict_type' de l'image sont' AV_PICTURE_TYPE_I', 'AV_PICTURE_TYPE_P',' AV_PICTURE_TYPE_B', et ainsi de suite ... –

1

vous aurez besoin de la bibliothèque libavcodec, Pour la première étape, je pense que vous pouvez en apprendre davantage sur son utilisation dans le fichier ffplay.c à l'intérieur du code source ffmpeg. Cela vous en dirait beaucoup. Vous pouvez également consulter mon projet sur la vidéo à l'adresse rtstegvideo.sourceforge.net.

Espérons cette aide.

1

Si vous êtes programmeur Java, utilisez Xuggler.

4

Une version mise à jour de api-example.c se trouvent à http://ffmpeg.org/doxygen/trunk/doc_2examples_2decoding_encoding_8c-example.html

Il fait l'encodage vidéo dans son intégralité en une seule et la fonction relativement courte. Donc, c'est probablement un bon point de départ. Compilez et exécutez-le. Et puis commencez à le modifier jusqu'à ce qu'il fasse ce que vous voulez.

Il dispose également d'un codage audio et audio & des exemples de décodage vidéo.

+0

Il est préférable de rechercher la documentation officielle pour la version mise à jour : http://ffmpeg.org/doxygen/trunk/doc_2examples_2decoding_encoding_8c-example.html –

0

exemple minimal sur runnable FFmpeg 2.7

Sur la base de Ori Pessach's réponse ci-dessous est un exemple minimal qui génère des trames de forme.

  • I
  • P
  • B
  • P
  • ...

Les éléments clés du code qui commandent le type de trame sont:

c = avcodec_alloc_context3(codec); 
/* Minimal distance of I-frames. This is the maximum value allowed, 
or else we get a warning at runtime. */ 
c->keyint_min = 600; 
/* Or else it defaults to 0 b-frames are not allowed. */ 
c->max_b_frames = 1; 

et:

frame->key_frame = 0; 
switch (frame->pts % 4) { 
    case 0: 
     frame->key_frame = 1; 
     frame->pict_type = AV_PICTURE_TYPE_I; 
    break; 
    case 1: 
    case 3: 
     frame->pict_type = AV_PICTURE_TYPE_P; 
    break; 
    case 2: 
     frame->pict_type = AV_PICTURE_TYPE_B; 
    break; 
} 

On peut alors vérifier le type de cadre avec:

ffprobe -select_streams v \ 
    -show_frames \ 
    -show_entries frame=pict_type \ 
    -of csv \ 
    tmp.h264 

comme mentionné à: https://superuser.com/questions/885452/extracting-the-index-of-key-frames-from-a-video-using-ffmpeg

Certaines règles ont été appliquées par FFmpeg même si je tente de les surmonter:

  • la première image est une image I
  • ne peut pas placer e une B0frame avant une trame I (TODO pourquoi?)

Preview of generated output.

#include <libavcodec/avcodec.h> 
#include <libavutil/imgutils.h> 
#include <libavutil/opt.h> 
#include <libswscale/swscale.h> 

static AVCodecContext *c = NULL; 
static AVFrame *frame; 
static AVPacket pkt; 
static FILE *file; 
struct SwsContext *sws_context = NULL; 

/* 
Convert RGB24 array to YUV. Save directly to the `frame`, 
modifying its `data` and `linesize` fields 
*/ 
static void ffmpeg_encoder_set_frame_yuv_from_rgb(uint8_t *rgb) { 
    const int in_linesize[1] = { 3 * c->width }; 
    sws_context = sws_getCachedContext(sws_context, 
      c->width, c->height, AV_PIX_FMT_RGB24, 
      c->width, c->height, AV_PIX_FMT_YUV420P, 
      0, 0, 0, 0); 
    sws_scale(sws_context, (const uint8_t * const *)&rgb, in_linesize, 0, 
      c->height, frame->data, frame->linesize); 
} 

/* 
Generate 2 different images with four colored rectangles, each 25 frames long: 

Image 1: 

    black | red 
    ------+----- 
    green | blue 

Image 2: 

    yellow | red 
    -------+----- 
    green | white 
*/ 
uint8_t* generate_rgb(int width, int height, int pts, uint8_t *rgb) { 
    int x, y, cur; 
    rgb = realloc(rgb, 3 * sizeof(uint8_t) * height * width); 
    for (y = 0; y < height; y++) { 
     for (x = 0; x < width; x++) { 
      cur = 3 * (y * width + x); 
      rgb[cur + 0] = 0; 
      rgb[cur + 1] = 0; 
      rgb[cur + 2] = 0; 
      if ((frame->pts/25) % 2 == 0) { 
       if (y < height/2) { 
        if (x < width/2) { 
         /* Black. */ 
        } else { 
         rgb[cur + 0] = 255; 
        } 
       } else { 
        if (x < width/2) { 
         rgb[cur + 1] = 255; 
        } else { 
         rgb[cur + 2] = 255; 
        } 
       } 
      } else { 
       if (y < height/2) { 
        rgb[cur + 0] = 255; 
        if (x < width/2) { 
         rgb[cur + 1] = 255; 
        } else { 
         rgb[cur + 2] = 255; 
        } 
       } else { 
        if (x < width/2) { 
         rgb[cur + 1] = 255; 
         rgb[cur + 2] = 255; 
        } else { 
         rgb[cur + 0] = 255; 
         rgb[cur + 1] = 255; 
         rgb[cur + 2] = 255; 
        } 
       } 
      } 
     } 
    } 
    return rgb; 
} 

/* Allocate resources and write header data to the output file. */ 
void ffmpeg_encoder_start(const char *filename, int codec_id, int fps, int width, int height) { 
    AVCodec *codec; 
    int ret; 
    codec = avcodec_find_encoder(codec_id); 
    if (!codec) { 
     fprintf(stderr, "Codec not found\n"); 
     exit(1); 
    } 
    c = avcodec_alloc_context3(codec); 
    if (!c) { 
     fprintf(stderr, "Could not allocate video codec context\n"); 
     exit(1); 
    } 
    c->bit_rate = 400000; 
    c->width = width; 
    c->height = height; 
    c->time_base.num = 1; 
    c->time_base.den = fps; 
    /* I, P, B frame placement parameters. */ 
    c->gop_size = 600; 
    c->max_b_frames = 1; 
    c->keyint_min = 600; 
    c->pix_fmt = AV_PIX_FMT_YUV420P; 
    if (codec_id == AV_CODEC_ID_H264) 
     av_opt_set(c->priv_data, "preset", "slow", 0); 
    if (avcodec_open2(c, codec, NULL) < 0) { 
     fprintf(stderr, "Could not open codec\n"); 
     exit(1); 
    } 
    file = fopen(filename, "wb"); 
    if (!file) { 
     fprintf(stderr, "Could not open %s\n", filename); 
     exit(1); 
    } 
    frame = av_frame_alloc(); 
    if (!frame) { 
     fprintf(stderr, "Could not allocate video frame\n"); 
     exit(1); 
    } 
    frame->format = c->pix_fmt; 
    frame->width = c->width; 
    frame->height = c->height; 
    ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height, c->pix_fmt, 32); 
    if (ret < 0) { 
     fprintf(stderr, "Could not allocate raw picture buffer\n"); 
     exit(1); 
    } 
} 

/* 
Write trailing data to the output file 
and free resources allocated by ffmpeg_encoder_start. 
*/ 
void ffmpeg_encoder_finish(void) { 
    uint8_t endcode[] = { 0, 0, 1, 0xb7 }; 
    int got_output, ret; 
    do { 
     fflush(stdout); 
     ret = avcodec_encode_video2(c, &pkt, NULL, &got_output); 
     if (ret < 0) { 
      fprintf(stderr, "Error encoding frame\n"); 
      exit(1); 
     } 
     if (got_output) { 
      fwrite(pkt.data, 1, pkt.size, file); 
      av_packet_unref(&pkt); 
     } 
    } while (got_output); 
    fwrite(endcode, 1, sizeof(endcode), file); 
    fclose(file); 
    avcodec_close(c); 
    av_free(c); 
    av_freep(&frame->data[0]); 
    av_frame_free(&frame); 
} 

/* 
Encode one frame from an RGB24 input and save it to the output file. 
Must be called after ffmpeg_encoder_start, and ffmpeg_encoder_finish 
must be called after the last call to this function. 
*/ 
void ffmpeg_encoder_encode_frame(uint8_t *rgb) { 
    int ret, got_output; 
    ffmpeg_encoder_set_frame_yuv_from_rgb(rgb); 
    av_init_packet(&pkt); 
    pkt.data = NULL; 
    pkt.size = 0; 
    switch (frame->pts % 4) { 
     case 0: 
      frame->key_frame = 1; 
      frame->pict_type = AV_PICTURE_TYPE_I; 
     break; 
     case 1: 
     case 3: 
      frame->key_frame = 0; 
      frame->pict_type = AV_PICTURE_TYPE_P; 
     break; 
     case 2: 
      frame->key_frame = 0; 
      frame->pict_type = AV_PICTURE_TYPE_B; 
     break; 
    } 
    ret = avcodec_encode_video2(c, &pkt, frame, &got_output); 
    if (ret < 0) { 
     fprintf(stderr, "Error encoding frame\n"); 
     exit(1); 
    } 
    if (got_output) { 
     fwrite(pkt.data, 1, pkt.size, file); 
     av_packet_unref(&pkt); 
    } 
} 

/* Represents the main loop of an application which generates one frame per loop. */ 
static void encode_example(const char *filename, int codec_id) { 
    int pts; 
    int width = 320; 
    int height = 240; 
    uint8_t *rgb = NULL; 
    ffmpeg_encoder_start(filename, codec_id, 25, width, height); 
    for (pts = 0; pts < 100; pts++) { 
     frame->pts = pts; 
     rgb = generate_rgb(width, height, pts, rgb); 
     ffmpeg_encoder_encode_frame(rgb); 
    } 
    ffmpeg_encoder_finish(); 
} 

int main(void) { 
    avcodec_register_all(); 
    encode_example("tmp.h264", AV_CODEC_ID_H264); 
    encode_example("tmp.mpg", AV_CODEC_ID_MPEG1VIDEO); 
    /* TODO: is this encoded correctly? Possible to view it without container? */ 
    /*encode_example("tmp.vp8", AV_CODEC_ID_VP8);*/ 
    return 0; 
} 

Testé sur Ubuntu 15.10. GitHub upstream.

Avez-vous vraiment voulez-vous faire cela?

Dans la plupart des cas, il vaut mieux simplement contrôler les paramètres globaux de AVCodecContext. FFmpeg fait des choses intelligentes comme l'utilisation d'une image clé si la nouvelle image est complètement différente de la précédente, et que l'encodage différentiel ne serait pas très utile.

Par exemple, si nous fixons simplement:

c->keyint_min = 600; 

alors nous obtenons exactement 4 images clés sur l'exemple ci-dessus, ce qui est logique car il y a 4 changements de cadre abrupts sur la vidéo générée.