Nous publions le reste de la traduction en russe du manuel, qui est quelque peu dépassé, mais n'a pas perdu de sa pertinence, car ce tutoriel permet de plonger dans la «cuisine» de la création d'applications vidéo à l'aide des bibliothÚques FFmpeg et SDL.Et bien que nous ayons essayé, les difficultés de traduction sont inévitables dans un texte aussi volumineux . Signalez les bugs (de préférence dans les messages privés) - ensemble, nous ferons mieux.Table des matiÚres

EDISON.
, .
, , Axxon Next SureView Immix.
! ;-)
tutorial06.c
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#include <SDL_thread.h>
#ifdef __MINGW32__
#undef main
#endif
#include <stdio.h>
#include <assert.h>
#include <math.h>
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif
#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000
#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)
#define AV_SYNC_THRESHOLD 0.01
#define AV_NOSYNC_THRESHOLD 10.0
#define SAMPLE_CORRECTION_PERCENT_MAX 10
#define AUDIO_DIFF_AVG_NB 20
#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)
#define VIDEO_PICTURE_QUEUE_SIZE 1
#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
typedef struct VideoPicture {
SDL_Overlay *bmp;
int width, height;
int allocated;
double pts;
} VideoPicture;
typedef struct VideoState {
AVFormatContext *pFormatCtx;
int videoStream, audioStream;
int av_sync_type;
double external_clock;
int64_t external_clock_time;
double audio_clock;
AVStream *audio_st;
AVCodecContext *audio_ctx;
PacketQueue audioq;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
unsigned int audio_buf_size;
unsigned int audio_buf_index;
AVFrame audio_frame;
AVPacket audio_pkt;
uint8_t *audio_pkt_data;
int audio_pkt_size;
int audio_hw_buf_size;
double audio_diff_cum;
double audio_diff_avg_coef;
double audio_diff_threshold;
int audio_diff_avg_count;
double frame_timer;
double frame_last_pts;
double frame_last_delay;
double video_clock;
double video_current_pts;
int64_t video_current_pts_time;
AVStream *video_st;
AVCodecContext *video_ctx;
PacketQueue videoq;
struct SwsContext *sws_ctx;
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
int pictq_size, pictq_rindex, pictq_windex;
SDL_mutex *pictq_mutex;
SDL_cond *pictq_cond;
SDL_Thread *parse_tid;
SDL_Thread *video_tid;
char filename[1024];
int quit;
} VideoState;
enum {
AV_SYNC_AUDIO_MASTER,
AV_SYNC_VIDEO_MASTER,
AV_SYNC_EXTERNAL_MASTER,
};
SDL_Surface *screen;
SDL_mutex *screen_mutex;
VideoState *global_video_state;
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if(av_dup_packet(pkt) < 0) {
return -1;
}
pkt1 = av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);
for(;;) {
if(global_video_state->quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
double get_audio_clock(VideoState *is) {
double pts;
int hw_buf_size, bytes_per_sec, n;
pts = is->audio_clock;
hw_buf_size = is->audio_buf_size - is->audio_buf_index;
bytes_per_sec = 0;
n = is->audio_ctx->channels * 2;
if(is->audio_st) {
bytes_per_sec = is->audio_ctx->sample_rate * n;
}
if(bytes_per_sec) {
pts -= (double)hw_buf_size / bytes_per_sec;
}
return pts;
}
double get_video_clock(VideoState *is) {
double delta;
delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;
return is->video_current_pts + delta;
}
double get_external_clock(VideoState *is) {
return av_gettime() / 1000000.0;
}
double get_master_clock(VideoState *is) {
if(is->av_sync_type == AV_SYNC_VIDEO_MASTER) {
return get_video_clock(is);
} else if(is->av_sync_type == AV_SYNC_AUDIO_MASTER) {
return get_audio_clock(is);
} else {
return get_external_clock(is);
}
}
int synchronize_audio(VideoState *is, short *samples,
int samples_size, double pts) {
int n;
double ref_clock;
n = 2 * is->audio_ctx->channels;
if(is->av_sync_type != AV_SYNC_AUDIO_MASTER) {
double diff, avg_diff;
int wanted_size, min_size, max_size ;
ref_clock = get_master_clock(is);
diff = get_audio_clock(is) - ref_clock;
if(diff < AV_NOSYNC_THRESHOLD) {
is->audio_diff_cum = diff + is->audio_diff_avg_coef
* is->audio_diff_cum;
if(is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
is->audio_diff_avg_count++;
} else {
avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
if(fabs(avg_diff) >= is->audio_diff_threshold) {
wanted_size = samples_size + ((int)(diff * is->audio_ctx->sample_rate) * n);
min_size = samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100);
max_size = samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100);
if(wanted_size < min_size) {
wanted_size = min_size;
} else if (wanted_size > max_size) {
wanted_size = max_size;
}
if(wanted_size < samples_size) {
samples_size = wanted_size;
} else if(wanted_size > samples_size) {
uint8_t *samples_end, *q;
int nb;
nb = (samples_size - wanted_size);
samples_end = (uint8_t *)samples + samples_size - n;
q = samples_end + n;
while(nb > 0) {
memcpy(q, samples_end, n);
q += n;
nb -= n;
}
samples_size = wanted_size;
}
}
}
} else {
is->audio_diff_avg_count = 0;
is->audio_diff_cum = 0;
}
}
return samples_size;
}
int audio_decode_frame(VideoState *is, uint8_t *audio_buf, int buf_size, double *pts_ptr) {
int len1, data_size = 0;
AVPacket *pkt = &is->audio_pkt;
double pts;
int n;
for(;;) {
while(is->audio_pkt_size > 0) {
int got_frame = 0;
len1 = avcodec_decode_audio4(is->audio_ctx, &is->audio_frame, &got_frame, pkt);
if(len1 < 0) {
is->audio_pkt_size = 0;
break;
}
data_size = 0;
if(got_frame) {
data_size = av_samples_get_buffer_size(NULL,
is->audio_ctx->channels,
is->audio_frame.nb_samples,
is->audio_ctx->sample_fmt,
1);
assert(data_size <= buf_size);
memcpy(audio_buf, is->audio_frame.data[0], data_size);
}
is->audio_pkt_data += len1;
is->audio_pkt_size -= len1;
if(data_size <= 0) {
continue;
}
pts = is->audio_clock;
*pts_ptr = pts;
n = 2 * is->audio_ctx->channels;
is->audio_clock += (double)data_size /
(double)(n * is->audio_ctx->sample_rate);
return data_size;
}
if(pkt->data)
av_free_packet(pkt);
if(is->quit) {
return -1;
}
if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
return -1;
}
is->audio_pkt_data = pkt->data;
is->audio_pkt_size = pkt->size;
if(pkt->pts != AV_NOPTS_VALUE) {
is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
}
}
}
void audio_callback(void *userdata, Uint8 *stream, int len) {
VideoState *is = (VideoState *)userdata;
int len1, audio_size;
double pts;
while(len > 0) {
if(is->audio_buf_index >= is->audio_buf_size) {
audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
if(audio_size < 0) {
is->audio_buf_size = 1024;
memset(is->audio_buf, 0, is->audio_buf_size);
} else {
audio_size = synchronize_audio(is, (int16_t *)is->audio_buf,
audio_size, pts);
is->audio_buf_size = audio_size;
}
is->audio_buf_index = 0;
}
len1 = is->audio_buf_size - is->audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
len -= len1;
stream += len1;
is->audio_buf_index += len1;
}
}
static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
SDL_Event event;
event.type = FF_REFRESH_EVENT;
event.user.data1 = opaque;
SDL_PushEvent(&event);
return 0;
}
static void schedule_refresh(VideoState *is, int delay) {
SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}
void video_display(VideoState *is) {
SDL_Rect rect;
VideoPicture *vp;
float aspect_ratio;
int w, h, x, y;
int i;
vp = &is->pictq[is->pictq_rindex];
if(vp->bmp) {
if(is->video_ctx->sample_aspect_ratio.num == 0) {
aspect_ratio = 0;
} else {
aspect_ratio = av_q2d(is->video_ctx->sample_aspect_ratio) *
is->video_ctx->width / is->video_ctx->height;
}
if(aspect_ratio <= 0.0) {
aspect_ratio = (float)is->video_ctx->width /
(float)is->video_ctx->height;
}
h = screen->h;
w = ((int)rint(h * aspect_ratio)) & -3;
if(w > screen->w) {
w = screen->w;
h = ((int)rint(w / aspect_ratio)) & -3;
}
x = (screen->w - w) / 2;
y = (screen->h - h) / 2;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_LockMutex(screen_mutex);
SDL_DisplayYUVOverlay(vp->bmp, &rect);
SDL_UnlockMutex(screen_mutex);
}
}
void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
double actual_delay, delay, sync_threshold, ref_clock, diff;
if(is->video_st) {
if(is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp = &is->pictq[is->pictq_rindex];
is->video_current_pts = vp->pts;
is->video_current_pts_time = av_gettime();
delay = vp->pts - is->frame_last_pts;
if(delay <= 0 || delay >= 1.0) {
delay = is->frame_last_delay;
}
is->frame_last_delay = delay;
is->frame_last_pts = vp->pts;
if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
ref_clock = get_master_clock(is);
diff = vp->pts - ref_clock;
sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
if(diff <= -sync_threshold) {
delay = 0;
} else if(diff >= sync_threshold) {
delay = 2 * delay;
}
}
}
is->frame_timer += delay;
actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
if(actual_delay < 0.010) {
actual_delay = 0.010;
}
schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
video_display(is);
if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size--;
SDL_CondSignal(is->pictq_cond);
SDL_UnlockMutex(is->pictq_mutex);
}
} else {
schedule_refresh(is, 100);
}
}
void alloc_picture(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
vp = &is->pictq[is->pictq_windex];
if(vp->bmp) {
SDL_FreeYUVOverlay(vp->bmp);
}
SDL_LockMutex(screen_mutex);
vp->bmp = SDL_CreateYUVOverlay(is->video_ctx->width,
is->video_ctx->height,
SDL_YV12_OVERLAY,
screen);
SDL_UnlockMutex(screen_mutex);
vp->width = is->video_ctx->width;
vp->height = is->video_ctx->height;
vp->allocated = 1;
}
int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {
VideoPicture *vp;
int dst_pix_fmt;
AVPicture pict;
SDL_LockMutex(is->pictq_mutex);
while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
!is->quit) {
SDL_CondWait(is->pictq_cond, is->pictq_mutex);
}
SDL_UnlockMutex(is->pictq_mutex);
if(is->quit)
return -1;
vp = &is->pictq[is->pictq_windex];
if(!vp->bmp ||
vp->width != is->video_ctx->width ||
vp->height != is->video_ctx->height) {
SDL_Event event;
vp->allocated = 0;
alloc_picture(is);
if(is->quit) {
return -1;
}
}
if(vp->bmp) {
SDL_LockYUVOverlay(vp->bmp);
vp->pts = pts;
dst_pix_fmt = PIX_FMT_YUV420P;
pict.data[0] = vp->bmp->pixels[0];
pict.data[1] = vp->bmp->pixels[2];
pict.data[2] = vp->bmp->pixels[1];
pict.linesize[0] = vp->bmp->pitches[0];
pict.linesize[1] = vp->bmp->pitches[2];
pict.linesize[2] = vp->bmp->pitches[1];
sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, is->video_ctx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(vp->bmp);
if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_windex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size++;
SDL_UnlockMutex(is->pictq_mutex);
}
return 0;
}
double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {
double frame_delay;
if(pts != 0) {
is->video_clock = pts;
} else {
pts = is->video_clock;
}
frame_delay = av_q2d(is->video_ctx->time_base);
frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
is->video_clock += frame_delay;
return pts;
}
int video_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVPacket pkt1, *packet = &pkt1;
int frameFinished;
AVFrame *pFrame;
double pts;
pFrame = av_frame_alloc();
for(;;) {
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
pts = 0;
avcodec_decode_video2(is->video_ctx, pFrame, &frameFinished, packet);
if((pts = av_frame_get_best_effort_timestamp(pFrame)) == AV_NOPTS_VALUE) {
} else {
pts = 0;
}
pts *= av_q2d(is->video_st->time_base);
if(frameFinished) {
pts = synchronize_video(is, pFrame, pts);
if(queue_picture(is, pFrame, pts) < 0) {
break;
}
}
av_free_packet(packet);
}
av_frame_free(&pFrame);
return 0;
}
int stream_component_open(VideoState *is, int stream_index) {
AVFormatContext *pFormatCtx = is->pFormatCtx;
AVCodecContext *codecCtx = NULL;
AVCodec *codec = NULL;
SDL_AudioSpec wanted_spec, spec;
if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
return -1;
}
codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codec->codec_id);
if(!codec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
wanted_spec.freq = codecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = codecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = is;
if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
is->audio_hw_buf_size = spec.size;
}
if(avcodec_open2(codecCtx, codec, NULL) < 0) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
switch(codecCtx->codec_type) {
case AVMEDIA_TYPE_AUDIO:
is->audioStream = stream_index;
is->audio_st = pFormatCtx->streams[stream_index];
is->audio_ctx = codecCtx;
is->audio_buf_size = 0;
is->audio_buf_index = 0;
memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
packet_queue_init(&is->audioq);
SDL_PauseAudio(0);
break;
case AVMEDIA_TYPE_VIDEO:
is->videoStream = stream_index;
is->video_st = pFormatCtx->streams[stream_index];
is->video_ctx = codecCtx;
is->frame_timer = (double)av_gettime() / 1000000.0;
is->frame_last_delay = 40e-3;
is->video_current_pts_time = av_gettime();
packet_queue_init(&is->videoq);
is->video_tid = SDL_CreateThread(video_thread, is);
is->sws_ctx = sws_getContext(is->video_ctx->width, is->video_ctx->height,
is->video_ctx->pix_fmt, is->video_ctx->width,
is->video_ctx->height, PIX_FMT_YUV420P,
SWS_BILINEAR, NULL, NULL, NULL
);
break;
default:
break;
}
}
int decode_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVFormatContext *pFormatCtx;
AVPacket pkt1, *packet = &pkt1;
int video_index = -1;
int audio_index = -1;
int i;
is->videoStream=-1;
is->audioStream=-1;
global_video_state = is;
if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
return -1;
is->pFormatCtx = pFormatCtx;
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
return -1;
av_dump_format(pFormatCtx, 0, is->filename, 0);
for(i=0; i<pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
video_index < 0) {
video_index=i;
}
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
audio_index < 0) {
audio_index=i;
}
}
if(audio_index >= 0) {
stream_component_open(is, audio_index);
}
if(video_index >= 0) {
stream_component_open(is, video_index);
}
if(is->videoStream < 0 || is->audioStream < 0) {
fprintf(stderr, "%s: could not open codecs\n", is->filename);
goto fail;
}
for(;;) {
if(is->quit) {
break;
}
if(is->audioq.size > MAX_AUDIOQ_SIZE ||
is->videoq.size > MAX_VIDEOQ_SIZE) {
SDL_Delay(10);
continue;
}
if(av_read_frame(is->pFormatCtx, packet) < 0) {
if(is->pFormatCtx->pb->error == 0) {
SDL_Delay(100);
continue;
} else {
break;
}
}
if(packet->stream_index == is->videoStream) {
packet_queue_put(&is->videoq, packet);
} else if(packet->stream_index == is->audioStream) {
packet_queue_put(&is->audioq, packet);
} else {
av_free_packet(packet);
}
}
while(!is->quit) {
SDL_Delay(100);
}
fail:
if(1){
SDL_Event event;
event.type = FF_QUIT_EVENT;
event.user.data1 = is;
SDL_PushEvent(&event);
}
return 0;
}
int main(int argc, char *argv[]) {
SDL_Event event;
VideoState *is;
is = av_mallocz(sizeof(VideoState));
if(argc < 2) {
fprintf(stderr, "Usage: test <file>\n");
exit(1);
}
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
#ifndef __DARWIN__
screen = SDL_SetVideoMode(640, 480, 0, 0);
#else
screen = SDL_SetVideoMode(640, 480, 24, 0);
#endif
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
screen_mutex = SDL_CreateMutex();
av_strlcpy(is->filename, argv[1], sizeof(is->filename));
is->pictq_mutex = SDL_CreateMutex();
is->pictq_cond = SDL_CreateCond();
schedule_refresh(is, 40);
is->av_sync_type = DEFAULT_AV_SYNC_TYPE;
is->parse_tid = SDL_CreateThread(decode_thread, is);
if(!is->parse_tid) {
av_free(is);
return -1;
}
for(;;) {
SDL_WaitEvent(&event);
switch(event.type) {
case FF_QUIT_EVENT:
case SDL_QUIT:
is->quit = 1;
SDL_Quit();
return 0;
break;
case FF_REFRESH_EVENT:
video_refresh_timer(event.user.data1);
break;
default:
break;
}
}
return 0;
}
Maintenant que nous avons un joueur plus ou moins dĂ©cent sur lequel vous pouvez mĂȘme regarder un film, nous allons maintenant joindre les deux bouts. La derniĂšre fois, nous avons lĂ©gĂšrement abordĂ© la synchronisation, Ă savoir la synchronisation du son avec la vidĂ©o, dans cet ordre, et non l'inverse. Nous allons faire cela de la mĂȘme disposition qu'avec la vidĂ©o: crĂ©er une horloge vidĂ©o interne pour suivre la distance du flux vidĂ©o et synchroniser l'audio avec. Plus tard, nous gĂ©nĂ©raliserons encore plus - nous synchroniserons l'audio et la vidĂ©o avec une horloge externe.ImplĂ©mentation de l'horloge vidĂ©o
Maintenant, nous voulons crĂ©er une horloge vidĂ©o similaire Ă l'horloge audio que nous avions la derniĂšre fois: une valeur interne qui renvoie le dĂ©calage horaire actuel de la vidĂ©o en cours de lecture. Vous pourriez penser que ce sera aussi simple que de mettre Ă jour le minuteur avec le PTS actuel de la derniĂšre image affichĂ©e. Cependant, n'oubliez pas que le temps entre les images vidĂ©o peut ĂȘtre trop long si nous passons au niveau de la milliseconde. Par consĂ©quent, la solution consiste Ă suivre une autre valeur, l'heure Ă laquelle nous avons rĂ©glĂ© l'horloge vidĂ©o sur le PTS de la derniĂšre image. Ainsi, la valeur actuelle de l' horloge vidĂ©o sera PTS_of_last_frame + ( current_time - time_elapsed_since_PTS_value_was_set) Cette solution est trĂšs similaire Ă ce que nous avons fait avec get_audio_clock .Donc, dans notre structure complĂšte, nous allons mettre double video_current_pts et int64_t video_current_pts_time . L'horloge sera mise Ă jour dans la fonction video_refresh_timer :void video_refresh_timer(void *userdata) {
if(is->video_st) {
if(is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp = &is->pictq[is->pictq_rindex];
is->video_current_pts = vp->pts;
is->video_current_pts_time = av_gettime();
N'oubliez pas de l'initialiser dans stream_component_open :is->video_current_pts_time = av_gettime();
Et maintenant, tout ce dont nous avons besoin est d'un moyen d'obtenir les informations:double get_video_clock(VideoState *is) {
double delta;
delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;
return is->video_current_pts + delta;
}
Abstrait de la montre
Mais pourquoi vous forcer Ă utiliser une horloge vidĂ©o? Vous pouvez aller plus loin et modifier notre code de synchronisation vidĂ©o afin que l'audio et la vidĂ©o n'essaient pas de se synchroniser. Imaginez quel gĂąchis ce sera si nous essayons de le faire avec une option de ligne de commande, comme dans FFplay. Donc, abstenons- nous: nous allons crĂ©er une nouvelle fonction wrapper, get_master_clock , qui vĂ©rifie la variable av_sync_type , puis appelle get_audio_clock , get_video_clock ou toute autre horloge qui pourrait l'utiliser. Nous pouvons mĂȘme utiliser une horloge d'ordinateur, que nous appelons get_external_clock :enum {
AV_SYNC_AUDIO_MASTER,
AV_SYNC_VIDEO_MASTER,
AV_SYNC_EXTERNAL_MASTER,
};
#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER
double get_master_clock(VideoState *is) {
if(is->av_sync_type == AV_SYNC_VIDEO_MASTER) {
return get_video_clock(is);
} else if(is->av_sync_type == AV_SYNC_AUDIO_MASTER) {
return get_audio_clock(is);
} else {
return get_external_clock(is);
}
}
main() {
...
is->av_sync_type = DEFAULT_AV_SYNC_TYPE;
...
}
Synchronisation audio
Maintenant, la partie la plus difficile: synchroniser l'audio avec l'horloge vidĂ©o. Notre stratĂ©gie consiste Ă mesurer oĂč se trouve l'audio, Ă le comparer avec l'horloge vidĂ©o, puis Ă dĂ©couvrir combien d'Ă©chantillons nous devons ajuster, c'est-Ă -dire, devons-nous accĂ©lĂ©rer en supprimant les Ă©chantillons ou ralentir en ajoutant?Nous exĂ©cutons la fonction synchronize_audio chaque fois que nous traitons chaque ensemble d'Ă©chantillons audio que nous obtenons afin de rĂ©duire ou d'augmenter correctement cet ensemble. Cependant, nous ne voulons pas synchroniser tout le temps, car le traitement audio se produit beaucoup plus souvent que le traitement des paquets vidĂ©o. Donc, nous allons dĂ©finir le nombre minimum d'appels consĂ©cutifs Ă la fonction synchronize_audioqui sont considĂ©rĂ©s comme non synchronisĂ©s avant que nous ne prenions la peine de faire quoi que ce soit. Bien entendu, comme la derniĂšre fois, la «dĂ©synchronisation» signifie que l'horloge audio et l'horloge vidĂ©o diffĂšrent d'une quantitĂ© supĂ©rieure au seuil de synchronisation.Nous allons donc utiliser un coefficient fractionnaire, disons, s , et maintenant, disons que nous avons obtenu Nensembles d'Ă©chantillons audio qui n'Ă©taient pas synchronisĂ©s. Le nombre d'Ă©chantillons que nous ne synchronisons pas peut Ă©galement varier considĂ©rablement, nous prenons donc la valeur moyenne de combien chacun d'eux n'est pas synchronisĂ©. Par exemple, le premier appel pourrait montrer que nous ne sommes pas synchronisĂ©s pendant 40 ms, le suivant pendant 50 ms et ainsi de suite. Mais nous n'allons pas prendre une moyenne simple, car les valeurs les plus rĂ©centes sont plus importantes que celles qui les prĂ©cĂšdent. Donc, nous allons utiliser un coefficient fractionnaire, disons, c , et rĂ©sumer les diffĂ©rences comme suit: diff_sum = new_diff + diff_sum * c . Lorsque nous sommes prĂȘts Ă trouver la diffĂ©rence moyenne, nous calculons simplement avg_diff =diff_sum * (1 - c ).Qu'est ce qui se passe ici? L'Ă©quation ressemble Ă une sorte de magie. Eh bien, il s'agit essentiellement d'une moyenne pondĂ©rĂ©e utilisant une sĂ©rie gĂ©omĂ©trique comme poids. Je ne sais pas s'il y a un nom pour ça (j'ai mĂȘme vĂ©rifiĂ© sur WikipĂ©dia!), Mais pour plus d'informations, voici une explication (ou ici: weightedmean.txt ).Voici Ă quoi ressemble notre fonction:
int synchronize_audio(VideoState *is, short *samples,
int samples_size, double pts) {
int n;
double ref_clock;
n = 2 * is->audio_st->codec->channels;
if(is->av_sync_type != AV_SYNC_AUDIO_MASTER) {
double diff, avg_diff;
int wanted_size, min_size, max_size, nb_samples;
ref_clock = get_master_clock(is);
diff = get_audio_clock(is) - ref_clock;
if(diff < AV_NOSYNC_THRESHOLD) {
is->audio_diff_cum = diff + is->audio_diff_avg_coef
* is->audio_diff_cum;
if(is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
is->audio_diff_avg_count++;
} else {
avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
}
} else {
is->audio_diff_avg_count = 0;
is->audio_diff_cum = 0;
}
}
return samples_size;
}
Donc tout va bien pour nous; nous savons approximativement à quel point le son n'est pas cohérent avec la vidéo ou avec ce que nous utilisons comme montre. Donc, calculons maintenant combien d'échantillons nous devons ajouter ou supprimer en plaçant ce code dans la section "Réduire / étendre le code tampon":if(fabs(avg_diff) >= is->audio_diff_threshold) {
wanted_size = samples_size +
((int)(diff * is->audio_st->codec->sample_rate) * n);
min_size = samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX)
/ 100);
max_size = samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX)
/ 100);
if(wanted_size < min_size) {
wanted_size = min_size;
} else if (wanted_size > max_size) {
wanted_size = max_size;
}
N'oubliez pas que audio_length * ( sample_rate * # of channel * 2) est le nombre d'échantillons en audio_length secondes d'audio. Par conséquent, le nombre d'échantillons que nous voulons sera égal au nombre d'échantillons que nous avons déjà , plus ou moins le nombre d'échantillons correspondant à la durée pendant laquelle le son a été joué. Nous fixerons également une limite sur l'ampleur de notre correction, car si nous modifions trop notre tampon, ce sera trop ennuyeux pour l'utilisateur.Correction du nombre d'échantillons
Maintenant, nous devons corriger le son. Vous avez peut-ĂȘtre remarquĂ© que notre fonction synchronize_audio renvoie une taille d'Ă©chantillon, qui nous indique ensuite le nombre d'octets Ă envoyer au flux. Il nous suffit donc d'ajuster la taille de l'Ă©chantillon Ă la valeur souhaitĂ©e. Cela fonctionne pour rĂ©duire la taille de l'Ă©chantillon. Mais si vous devez l'augmenter, nous ne pouvons pas simplement augmenter la taille de l'Ă©chantillon, car il n'y a plus de donnĂ©es dans le tampon! Par consĂ©quent, nous devons ajouter un peu. Mais quoi ajouter exactement? Il serait stupide d'essayer d'extrapoler l'audio, alors utilisons simplement l'audio que nous avons dĂ©jĂ , en ajoutant la valeur du dernier Ă©chantillon au tampon.if(wanted_size < samples_size) {
samples_size = wanted_size;
} else if(wanted_size > samples_size) {
uint8_t *samples_end, *q;
int nb;
nb = (samples_size - wanted_size);
samples_end = (uint8_t *)samples + samples_size - n;
q = samples_end + n;
while(nb > 0) {
memcpy(q, samples_end, n);
q += n;
nb -= n;
}
samples_size = wanted_size;
}
Maintenant, nous retournons la taille de l'échantillon, et nous avons terminé avec cette fonction. Il ne nous reste plus qu'à utiliser ceci:void audio_callback(void *userdata, Uint8 *stream, int len) {
VideoState *is = (VideoState *)userdata;
int len1, audio_size;
double pts;
while(len > 0) {
if(is->audio_buf_index >= is->audio_buf_size) {
audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
if(audio_size < 0) {
is->audio_buf_size = 1024;
memset(is->audio_buf, 0, is->audio_buf_size);
} else {
audio_size = synchronize_audio(is, (int16_t *)is->audio_buf,
audio_size, pts);
is->audio_buf_size = audio_size;
Tout ce que nous avons fait, c'est insérer un appel synchronize_audio . (Aussi, assurez-vous de vérifier le code source, dans lequel nous initialisons les variables que je n'ai pas pris la peine de définir.)Et la derniÚre, avant de terminer: nous devons ajouter la condition "si" pour nous assurer de ne pas synchroniser la vidéo si c'est l'horloge principale:if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
ref_clock = get_master_clock(is);
diff = vp->pts - ref_clock;
sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay :
AV_SYNC_THRESHOLD;
if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
if(diff <= -sync_threshold) {
delay = 0;
} else if(diff >= sync_threshold) {
delay = 2 * delay;
}
}
}
Et il fonctionne! Assurez-vous de vérifier le fichier source pour initialiser toutes les variables que je n'ai pas pris la peine de définir ou d'initialiser. Compilez ensuite:gcc -o tutorial06 tutorial06.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`
et le vol sera normal.Dans la derniÚre leçon, nous allons revenir en arriÚre.
Leçon 7: Recherche â â â
Liste complĂšte tutorial07.c
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#include <SDL_thread.h>
#ifdef __MINGW32__
#undef main
#endif
#include <stdio.h>
#include <assert.h>
#include <math.h>
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif
#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000
#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)
#define AV_SYNC_THRESHOLD 0.01
#define AV_NOSYNC_THRESHOLD 10.0
#define SAMPLE_CORRECTION_PERCENT_MAX 10
#define AUDIO_DIFF_AVG_NB 20
#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)
#define VIDEO_PICTURE_QUEUE_SIZE 1
#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex;
SDL_cond *cond;
} PacketQueue;
typedef struct VideoPicture {
SDL_Overlay *bmp;
int width, height;
int allocated;
double pts;
} VideoPicture;
typedef struct VideoState {
AVFormatContext *pFormatCtx;
int videoStream, audioStream;
int av_sync_type;
double external_clock;
int64_t external_clock_time;
int seek_req;
int seek_flags;
int64_t seek_pos;
double audio_clock;
AVStream *audio_st;
AVCodecContext *audio_ctx;
PacketQueue audioq;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
unsigned int audio_buf_size;
unsigned int audio_buf_index;
AVFrame audio_frame;
AVPacket audio_pkt;
uint8_t *audio_pkt_data;
int audio_pkt_size;
int audio_hw_buf_size;
double audio_diff_cum;
double audio_diff_avg_coef;
double audio_diff_threshold;
int audio_diff_avg_count;
double frame_timer;
double frame_last_pts;
double frame_last_delay;
double video_clock;
double video_current_pts;
int64_t video_current_pts_time;
AVStream *video_st;
AVCodecContext *video_ctx;
PacketQueue videoq;
struct SwsContext *sws_ctx;
VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];
int pictq_size, pictq_rindex, pictq_windex;
SDL_mutex *pictq_mutex;
SDL_cond *pictq_cond;
SDL_Thread *parse_tid;
SDL_Thread *video_tid;
char filename[1024];
int quit;
} VideoState;
enum {
AV_SYNC_AUDIO_MASTER,
AV_SYNC_VIDEO_MASTER,
AV_SYNC_EXTERNAL_MASTER,
};
SDL_Surface *screen;
SDL_mutex *screen_mutex;
VideoState *global_video_state;
AVPacket flush_pkt;
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if(pkt != &flush_pkt && av_dup_packet(pkt) < 0) {
return -1;
}
pkt1 = av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);
for(;;) {
if(global_video_state->quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
static void packet_queue_flush(PacketQueue *q) {
AVPacketList *pkt, *pkt1;
SDL_LockMutex(q->mutex);
for(pkt = q->first_pkt; pkt != NULL; pkt = pkt1) {
pkt1 = pkt->next;
av_free_packet(&pkt->pkt);
av_freep(&pkt);
}
q->last_pkt = NULL;
q->first_pkt = NULL;
q->nb_packets = 0;
q->size = 0;
SDL_UnlockMutex(q->mutex);
}
double get_audio_clock(VideoState *is) {
double pts;
int hw_buf_size, bytes_per_sec, n;
pts = is->audio_clock;
hw_buf_size = is->audio_buf_size - is->audio_buf_index;
bytes_per_sec = 0;
n = is->audio_ctx->channels * 2;
if(is->audio_st) {
bytes_per_sec = is->audio_ctx->sample_rate * n;
}
if(bytes_per_sec) {
pts -= (double)hw_buf_size / bytes_per_sec;
}
return pts;
}
double get_video_clock(VideoState *is) {
double delta;
delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;
return is->video_current_pts + delta;
}
double get_external_clock(VideoState *is) {
return av_gettime() / 1000000.0;
}
double get_master_clock(VideoState *is) {
if(is->av_sync_type == AV_SYNC_VIDEO_MASTER) {
return get_video_clock(is);
} else if(is->av_sync_type == AV_SYNC_AUDIO_MASTER) {
return get_audio_clock(is);
} else {
return get_external_clock(is);
}
}
int synchronize_audio(VideoState *is, short *samples,
int samples_size, double pts) {
int n;
double ref_clock;
n = 2 * is->audio_ctx->channels;
if(is->av_sync_type != AV_SYNC_AUDIO_MASTER) {
double diff, avg_diff;
int wanted_size, min_size, max_size ;
ref_clock = get_master_clock(is);
diff = get_audio_clock(is) - ref_clock;
if(diff < AV_NOSYNC_THRESHOLD) {
is->audio_diff_cum = diff + is->audio_diff_avg_coef
* is->audio_diff_cum;
if(is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
is->audio_diff_avg_count++;
} else {
avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
if(fabs(avg_diff) >= is->audio_diff_threshold) {
wanted_size = samples_size + ((int)(diff * is->audio_ctx->sample_rate) * n);
min_size = samples_size * ((100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100);
max_size = samples_size * ((100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100);
if(wanted_size < min_size) {
wanted_size = min_size;
} else if (wanted_size > max_size) {
wanted_size = max_size;
}
if(wanted_size < samples_size) {
samples_size = wanted_size;
} else if(wanted_size > samples_size) {
uint8_t *samples_end, *q;
int nb;
nb = (samples_size - wanted_size);
samples_end = (uint8_t *)samples + samples_size - n;
q = samples_end + n;
while(nb > 0) {
memcpy(q, samples_end, n);
q += n;
nb -= n;
}
samples_size = wanted_size;
}
}
}
} else {
is->audio_diff_avg_count = 0;
is->audio_diff_cum = 0;
}
}
return samples_size;
}
int audio_decode_frame(VideoState *is, uint8_t *audio_buf, int buf_size, double *pts_ptr) {
int len1, data_size = 0;
AVPacket *pkt = &is->audio_pkt;
double pts;
int n;
for(;;) {
while(is->audio_pkt_size > 0) {
int got_frame = 0;
len1 = avcodec_decode_audio4(is->audio_ctx, &is->audio_frame, &got_frame, pkt);
if(len1 < 0) {
is->audio_pkt_size = 0;
break;
}
data_size = 0;
if(got_frame) {
data_size = av_samples_get_buffer_size(NULL,
is->audio_ctx->channels,
is->audio_frame.nb_samples,
is->audio_ctx->sample_fmt,
1);
assert(data_size <= buf_size);
memcpy(audio_buf, is->audio_frame.data[0], data_size);
}
is->audio_pkt_data += len1;
is->audio_pkt_size -= len1;
if(data_size <= 0) {
continue;
}
pts = is->audio_clock;
*pts_ptr = pts;
n = 2 * is->audio_ctx->channels;
is->audio_clock += (double)data_size /
(double)(n * is->audio_ctx->sample_rate);
return data_size;
}
if(pkt->data)
av_free_packet(pkt);
if(is->quit) {
return -1;
}
if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
return -1;
}
if(pkt->data == flush_pkt.data) {
avcodec_flush_buffers(is->audio_ctx);
continue;
}
is->audio_pkt_data = pkt->data;
is->audio_pkt_size = pkt->size;
if(pkt->pts != AV_NOPTS_VALUE) {
is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
}
}
}
void audio_callback(void *userdata, Uint8 *stream, int len) {
VideoState *is = (VideoState *)userdata;
int len1, audio_size;
double pts;
while(len > 0) {
if(is->audio_buf_index >= is->audio_buf_size) {
audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
if(audio_size < 0) {
is->audio_buf_size = 1024;
memset(is->audio_buf, 0, is->audio_buf_size);
} else {
audio_size = synchronize_audio(is, (int16_t *)is->audio_buf,
audio_size, pts);
is->audio_buf_size = audio_size;
}
is->audio_buf_index = 0;
}
len1 = is->audio_buf_size - is->audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
len -= len1;
stream += len1;
is->audio_buf_index += len1;
}
}
static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
SDL_Event event;
event.type = FF_REFRESH_EVENT;
event.user.data1 = opaque;
SDL_PushEvent(&event);
return 0;
}
static void schedule_refresh(VideoState *is, int delay) {
SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}
void video_display(VideoState *is) {
SDL_Rect rect;
VideoPicture *vp;
float aspect_ratio;
int w, h, x, y;
int i;
vp = &is->pictq[is->pictq_rindex];
if(vp->bmp) {
if(is->video_ctx->sample_aspect_ratio.num == 0) {
aspect_ratio = 0;
} else {
aspect_ratio = av_q2d(is->video_ctx->sample_aspect_ratio) *
is->video_ctx->width / is->video_ctx->height;
}
if(aspect_ratio <= 0.0) {
aspect_ratio = (float)is->video_ctx->width /
(float)is->video_ctx->height;
}
h = screen->h;
w = ((int)rint(h * aspect_ratio)) & -3;
if(w > screen->w) {
w = screen->w;
h = ((int)rint(w / aspect_ratio)) & -3;
}
x = (screen->w - w) / 2;
y = (screen->h - h) / 2;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_LockMutex(screen_mutex);
SDL_DisplayYUVOverlay(vp->bmp, &rect);
SDL_UnlockMutex(screen_mutex);
}
}
void video_refresh_timer(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
double actual_delay, delay, sync_threshold, ref_clock, diff;
if(is->video_st) {
if(is->pictq_size == 0) {
schedule_refresh(is, 1);
} else {
vp = &is->pictq[is->pictq_rindex];
is->video_current_pts = vp->pts;
is->video_current_pts_time = av_gettime();
delay = vp->pts - is->frame_last_pts;
if(delay <= 0 || delay >= 1.0) {
delay = is->frame_last_delay;
}
is->frame_last_delay = delay;
is->frame_last_pts = vp->pts;
if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
ref_clock = get_master_clock(is);
diff = vp->pts - ref_clock;
sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
if(diff <= -sync_threshold) {
delay = 0;
} else if(diff >= sync_threshold) {
delay = 2 * delay;
}
}
}
is->frame_timer += delay;
actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
if(actual_delay < 0.010) {
actual_delay = 0.010;
}
schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
video_display(is);
if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_rindex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size--;
SDL_CondSignal(is->pictq_cond);
SDL_UnlockMutex(is->pictq_mutex);
}
} else {
schedule_refresh(is, 100);
}
}
void alloc_picture(void *userdata) {
VideoState *is = (VideoState *)userdata;
VideoPicture *vp;
vp = &is->pictq[is->pictq_windex];
if(vp->bmp) {
SDL_FreeYUVOverlay(vp->bmp);
}
SDL_LockMutex(screen_mutex);
vp->bmp = SDL_CreateYUVOverlay(is->video_ctx->width,
is->video_ctx->height,
SDL_YV12_OVERLAY,
screen);
SDL_UnlockMutex(screen_mutex);
vp->width = is->video_ctx->width;
vp->height = is->video_ctx->height;
vp->allocated = 1;
}
int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {
VideoPicture *vp;
int dst_pix_fmt;
AVPicture pict;
SDL_LockMutex(is->pictq_mutex);
while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
!is->quit) {
SDL_CondWait(is->pictq_cond, is->pictq_mutex);
}
SDL_UnlockMutex(is->pictq_mutex);
if(is->quit)
return -1;
vp = &is->pictq[is->pictq_windex];
if(!vp->bmp ||
vp->width != is->video_ctx->width ||
vp->height != is->video_ctx->height) {
SDL_Event event;
vp->allocated = 0;
alloc_picture(is);
if(is->quit) {
return -1;
}
}
if(vp->bmp) {
SDL_LockYUVOverlay(vp->bmp);
vp->pts = pts;
dst_pix_fmt = PIX_FMT_YUV420P;
pict.data[0] = vp->bmp->pixels[0];
pict.data[1] = vp->bmp->pixels[2];
pict.data[2] = vp->bmp->pixels[1];
pict.linesize[0] = vp->bmp->pitches[0];
pict.linesize[1] = vp->bmp->pitches[2];
pict.linesize[2] = vp->bmp->pitches[1];
sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, 0, is->video_ctx->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(vp->bmp);
if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
is->pictq_windex = 0;
}
SDL_LockMutex(is->pictq_mutex);
is->pictq_size++;
SDL_UnlockMutex(is->pictq_mutex);
}
return 0;
}
double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {
double frame_delay;
if(pts != 0) {
is->video_clock = pts;
} else {
pts = is->video_clock;
}
frame_delay = av_q2d(is->video_ctx->time_base);
frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
is->video_clock += frame_delay;
return pts;
}
int video_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVPacket pkt1, *packet = &pkt1;
int frameFinished;
AVFrame *pFrame;
double pts;
pFrame = av_frame_alloc();
for(;;) {
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
if(packet_queue_get(&is->videoq, packet, 1) < 0) {
break;
}
pts = 0;
avcodec_decode_video2(is->video_ctx, pFrame, &frameFinished, packet);
if((pts = av_frame_get_best_effort_timestamp(pFrame)) == AV_NOPTS_VALUE) {
pts = av_frame_get_best_effort_timestamp(pFrame);
} else {
pts = 0;
}
pts *= av_q2d(is->video_st->time_base);
if(frameFinished) {
pts = synchronize_video(is, pFrame, pts);
if(queue_picture(is, pFrame, pts) < 0) {
break;
}
}
av_free_packet(packet);
}
av_frame_free(&pFrame);
return 0;
}
int stream_component_open(VideoState *is, int stream_index) {
AVFormatContext *pFormatCtx = is->pFormatCtx;
AVCodecContext *codecCtx = NULL;
AVCodec *codec = NULL;
SDL_AudioSpec wanted_spec, spec;
if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
return -1;
}
codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codec->codec_id);
if(!codec) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
fprintf(stderr, "Couldn't copy codec context");
return -1;
}
if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
wanted_spec.freq = codecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = codecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = is;
if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
return -1;
}
is->audio_hw_buf_size = spec.size;
}
if(avcodec_open2(codecCtx, codec, NULL) < 0) {
fprintf(stderr, "Unsupported codec!\n");
return -1;
}
switch(codecCtx->codec_type) {
case AVMEDIA_TYPE_AUDIO:
is->audioStream = stream_index;
is->audio_st = pFormatCtx->streams[stream_index];
is->audio_ctx = codecCtx;
is->audio_buf_size = 0;
is->audio_buf_index = 0;
memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
packet_queue_init(&is->audioq);
SDL_PauseAudio(0);
break;
case AVMEDIA_TYPE_VIDEO:
is->videoStream = stream_index;
is->video_st = pFormatCtx->streams[stream_index];
is->video_ctx = codecCtx;
is->frame_timer = (double)av_gettime() / 1000000.0;
is->frame_last_delay = 40e-3;
is->video_current_pts_time = av_gettime();
packet_queue_init(&is->videoq);
is->video_tid = SDL_CreateThread(video_thread, is);
is->sws_ctx = sws_getContext(is->video_ctx->width, is->video_ctx->height,
is->video_ctx->pix_fmt, is->video_ctx->width,
is->video_ctx->height, PIX_FMT_YUV420P,
SWS_BILINEAR, NULL, NULL, NULL
);
break;
default:
break;
}
}
int decode_thread(void *arg) {
VideoState *is = (VideoState *)arg;
AVFormatContext *pFormatCtx;
AVPacket pkt1, *packet = &pkt1;
int video_index = -1;
int audio_index = -1;
int i;
is->videoStream=-1;
is->audioStream=-1;
global_video_state = is;
if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
return -1;
is->pFormatCtx = pFormatCtx;
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
return -1;
av_dump_format(pFormatCtx, 0, is->filename, 0);
for(i=0; i<pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
video_index < 0) {
video_index=i;
}
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
audio_index < 0) {
audio_index=i;
}
}
if(audio_index >= 0) {
stream_component_open(is, audio_index);
}
if(video_index >= 0) {
stream_component_open(is, video_index);
}
if(is->videoStream < 0 || is->audioStream < 0) {
fprintf(stderr, "%s: could not open codecs\n", is->filename);
goto fail;
}
for(;;) {
if(is->quit) {
break;
}
if(is->seek_req) {
int stream_index= -1;
int64_t seek_target = is->seek_pos;
if (is->videoStream >= 0) stream_index = is->videoStream;
else if(is->audioStream >= 0) stream_index = is->audioStream;
if(stream_index>=0){
seek_target= av_rescale_q(seek_target, AV_TIME_BASE_Q,
pFormatCtx->streams[stream_index]->time_base);
}
if(av_seek_frame(is->pFormatCtx, stream_index,
seek_target, is->seek_flags) < 0) {
fprintf(stderr, "%s: error while seeking\n",
is->pFormatCtx->filename);
} else {
if(is->audioStream >= 0) {
packet_queue_flush(&is->audioq);
packet_queue_put(&is->audioq, &flush_pkt);
}
if(is->videoStream >= 0) {
packet_queue_flush(&is->videoq);
packet_queue_put(&is->videoq, &flush_pkt);
}
}
is->seek_req = 0;
}
if(is->audioq.size > MAX_AUDIOQ_SIZE ||
is->videoq.size > MAX_VIDEOQ_SIZE) {
SDL_Delay(10);
continue;
}
if(av_read_frame(is->pFormatCtx, packet) < 0) {
if(is->pFormatCtx->pb->error == 0) {
SDL_Delay(100);
continue;
} else {
break;
}
}
if(packet->stream_index == is->videoStream) {
packet_queue_put(&is->videoq, packet);
} else if(packet->stream_index == is->audioStream) {
packet_queue_put(&is->audioq, packet);
} else {
av_free_packet(packet);
}
}
while(!is->quit) {
SDL_Delay(100);
}
fail:
if(1){
SDL_Event event;
event.type = FF_QUIT_EVENT;
event.user.data1 = is;
SDL_PushEvent(&event);
}
return 0;
}
void stream_seek(VideoState *is, int64_t pos, int rel) {
if(!is->seek_req) {
is->seek_pos = pos;
is->seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0;
is->seek_req = 1;
}
}
int main(int argc, char *argv[]) {
SDL_Event event;
VideoState *is;
is = av_mallocz(sizeof(VideoState));
if(argc < 2) {
fprintf(stderr, "Usage: test <file>\n");
exit(1);
}
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
#ifndef __DARWIN__
screen = SDL_SetVideoMode(640, 480, 0, 0);
#else
screen = SDL_SetVideoMode(640, 480, 24, 0);
#endif
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
screen_mutex = SDL_CreateMutex();
av_strlcpy(is->filename, argv[1], sizeof(is->filename));
is->pictq_mutex = SDL_CreateMutex();
is->pictq_cond = SDL_CreateCond();
schedule_refresh(is, 40);
is->av_sync_type = DEFAULT_AV_SYNC_TYPE;
is->parse_tid = SDL_CreateThread(decode_thread, is);
if(!is->parse_tid) {
av_free(is);
return -1;
}
av_init_packet(&flush_pkt);
flush_pkt.data = "FLUSH";
for(;;) {
double incr, pos;
SDL_WaitEvent(&event);
switch(event.type) {
case SDL_KEYDOWN:
switch(event.key.keysym.sym) {
case SDLK_LEFT:
incr = -10.0;
goto do_seek;
case SDLK_RIGHT:
incr = 10.0;
goto do_seek;
case SDLK_UP:
incr = 60.0;
goto do_seek;
case SDLK_DOWN:
incr = -60.0;
goto do_seek;
do_seek:
if(global_video_state) {
pos = get_master_clock(global_video_state);
pos += incr;
stream_seek(global_video_state, (int64_t)(pos * AV_TIME_BASE), incr);
}
break;
default:
break;
}
break;
case FF_QUIT_EVENT:
case SDL_QUIT:
is->quit = 1;
SDL_CondSignal(is->audioq.cond);
SDL_CondSignal(is->videoq.cond);
SDL_Quit();
return 0;
break;
case FF_REFRESH_EVENT:
video_refresh_timer(event.user.data1);
break;
default:
break;
}
}
return 0;
}
Traitement des commandes de recherche
Maintenant, nous allons ajouter des fonctionnalitĂ©s de recherche dans notre lecteur, car c'est vraiment ennuyeux lorsque vous ne pouvez pas rembobiner le film. De plus, nous verrons Ă quel point il est facile d'utiliser la fonction av_seek_frame .Nous allons faire «flĂ©chir» vers la gauche et la «droite» les flĂšches du clavier pour faire avancer et reculer un peu le film, et les flĂšches «haut» et «bas» sont dĂ©jĂ plus importantes. «Un peu» - ce sera 10 secondes et «beaucoup» - tous les 60. Par consĂ©quent, nous devons configurer notre boucle principale afin qu'elle intercepte les Ă©vĂ©nements de frappe. Mais le fait est que lorsque nous obtenons une frappe, nous ne pouvons pas appeler directement av_seek_frame . Cela doit ĂȘtre fait dans notre boucle de dĂ©codage principale, boucle decode_thread. Par consĂ©quent, Ă la place, nous ajouterons quelques valeurs Ă la structure principale, qui contiendront une nouvelle position pour la recherche et quelques indicateurs de recherche: int seek_req;
int seek_flags;
int64_t seek_pos;
Maintenant, nous devons configurer notre boucle principale qui capture les frappes: for(;;) {
double incr, pos;
SDL_WaitEvent(&event);
switch(event.type) {
case SDL_KEYDOWN:
switch(event.key.keysym.sym) {
case SDLK_LEFT:
incr = -10.0;
goto do_seek;
case SDLK_RIGHT:
incr = 10.0;
goto do_seek;
case SDLK_UP:
incr = 60.0;
goto do_seek;
case SDLK_DOWN:
incr = -60.0;
goto do_seek;
do_seek:
if(global_video_state) {
pos = get_master_clock(global_video_state);
pos += incr;
stream_seek(global_video_state,
(int64_t)(pos * AV_TIME_BASE), incr);
}
break;
default:
break;
}
break;
Pour intercepter une frappe, nous examinons d'abord si l'événement SDL_KEYDOWN s'est produit . Ensuite, nous vérifions quelle clé est reçue à l'aide de event.key.keysym.sym . DÚs que nous découvrons dans quelle direction nous cherchons, nous calculons une nouvelle heure, en ajoutant un incrément à la valeur de notre nouvelle fonction get_master_clock . Ensuite , nous appelons la stream_seek fonction pour définir les seek_pos valeurs , etc. Convertissez notre nouvelle heure en unités d' horodatage interne avcodec . Rappelez-vous que les horodatages dans les flux sont mesurés en images, pas en secondes, en utilisant la formule suivante: secondes = images * base de temps ( fps ).Par défaut, avcodec est défini sur 1 000 000 d'images par seconde (donc une position de 2 secondes aura un horodatage de 2 000 000). Pourquoi devons-nous convertir cette valeur - voir plus loin.Voici notre fonction stream_seek . Notez que nous mettons le drapeau si nous revenons en arriÚre:void stream_seek(VideoState *is, int64_t pos, int rel) {
if(!is->seek_req) {
is->seek_pos = pos;
is->seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0;
is->seek_req = 1;
}
}
Passons maintenant Ă notre decode_thread , oĂč nous effectuons rĂ©ellement une recherche. Dans les fichiers source, vous pouvez voir que nous avons marquĂ© la zone "la recherche est en cours". Eh bien, nous allons le mettre lĂ tout de suite.La recherche est centrĂ©e sur la fonction av_seek_frame . Cette fonction prend comme argument le contexte de format, le flux, l'horodatage et le jeu d'indicateurs. La fonction recherchera l'horodatage que vous lui attribuez. L'unitĂ© d'horodatage est la base de temps du flux que vous passez Ă la fonction. Cependant, vous n'avez pas besoin de le transmettre au flux (indiquĂ© en passant la valeur -1). Si vous faites cela, time_base sera dans l' unitĂ© de temps interne avcodecou 1000000fps. C'est pourquoi nous avons multipliĂ© notre position par AV_TIME_BASE lorsque nous avons dĂ©fini Seek_pos .Cependant, parfois, vous pouvez (rarement) rencontrer des problĂšmes pour certains fichiers si vous passez av_seek_frame - 1 pour le flux, nous allons donc sĂ©lectionner le premier flux dans notre fichier et le transmettre Ă av_seek_frame . N'oubliez pas que nous devons changer l'Ă©chelle de notre horodatage pour ĂȘtre dans le nouveau «systĂšme de coordonnĂ©es».if(is->seek_req) {
int stream_index= -1;
int64_t seek_target = is->seek_pos;
if (is->videoStream >= 0) stream_index = is->videoStream;
else if(is->audioStream >= 0) stream_index = is->audioStream;
if(stream_index>=0){
seek_target= av_rescale_q(seek_target, AV_TIME_BASE_Q,
pFormatCtx->streams[stream_index]->time_base);
}
if(av_seek_frame(is->pFormatCtx, stream_index,
seek_target, is->seek_flags) < 0) {
fprintf(stderr, "%s: error while seeking\n",
is->pFormatCtx->filename);
} else {
av_rescale_q ( a , b , c ) est une fonction qui met à l'échelle l'horodatage d'une base à une autre. Il calcule essentiellement a * b / c , mais cette fonction est utile car ce calcul conduit parfois à un débordement. AV_TIME_BASE_Q est une version fractionnée d' AV_TIME_BASE . Ils sont complÚtement différents: AV_TIME_BASE * time_in_seconds = avcodec_timestamp et AV_TIME_BASE_Q * avcodec_timestamp = time_in_seconds (mais notez que AV_TIME_BASE_Qest en fait un objet AVRational , vous devez donc utiliser des fonctions q spéciales dans avcodec pour le traiter).Nettoyage des tampons
Nous avons donc configurĂ© nos recherches correctement, mais nous n'avons pas encore terminĂ©. N'oubliez pas, avons-nous une file d'attente configurĂ©e pour accumuler des paquets? Maintenant que nous sommes dans un horodatage diffĂ©rent, nous devons effacer cette file d'attente, sinon la recherche dans le film ne fonctionnera pas! De plus, avcodec possĂšde ses propres tampons internes, qui doivent Ă©galement ĂȘtre vidĂ©s pour chaque flux.Pour ce faire, vous devez d'abord Ă©crire une fonction qui efface notre file d'attente de paquets. Ensuite, vous devez en quelque sorte indiquer au flux audio et vidĂ©o qu'ils ont effacĂ© les tampons internes avcodec . Nous pouvons le faire en plaçant un paquet spĂ©cial dans la file d'attente aprĂšs son nettoyage, et quand ils (threads) dĂ©couvriront ce paquet spĂ©cial, ils effaceront simplement leurs tampons.Commençons par la fonction de rĂ©initialisation. C'est vraiment assez simple, donc je vais juste vous montrer le code:static void packet_queue_flush(PacketQueue *q) {
AVPacketList *pkt, *pkt1;
SDL_LockMutex(q->mutex);
for(pkt = q->first_pkt; pkt != NULL; pkt = pkt1) {
pkt1 = pkt->next;
av_free_packet(&pkt->pkt);
av_freep(&pkt);
}
q->last_pkt = NULL;
q->first_pkt = NULL;
q->nb_packets = 0;
q->size = 0;
SDL_UnlockMutex(q->mutex);
}
Maintenant que la file d'attente est effacée, ajoutez notre «paquet de nettoyage». Mais d'abord, il serait bien de définir ce que c'est et de le créer:AVPacket flush_pkt;
main() {
...
av_init_packet(&flush_pkt);
flush_pkt.data = "FLUSH";
...
}
Maintenant, mettez ce paquet dans la file d'attente: } else {
if(is->audioStream >= 0) {
packet_queue_flush(&is->audioq);
packet_queue_put(&is->audioq, &flush_pkt);
}
if(is->videoStream >= 0) {
packet_queue_flush(&is->videoq);
packet_queue_put(&is->videoq, &flush_pkt);
}
}
is->seek_req = 0;
}
(Cet extrait de code continue l'extrait de code ci-dessus pour decode_thread .) Nous devons également modifier packet_queue_put afin de ne pas dupliquer un package spécial pour le nettoyage:int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if(pkt != &flush_pkt && av_dup_packet(pkt) < 0) {
return -1;
}
Et puis dans le flux audio et vidéo, nous plaçons cet appel dans avcodec_flush_buffers juste aprÚs packet_queue_get : if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
return -1;
}
if(pkt->data == flush_pkt.data) {
avcodec_flush_buffers(is->audio_st->codec);
continue;
}
L'extrait de code ci-dessus est exactement le mĂȘme pour le flux vidĂ©o, avec le remplacement de "audio" par "video".Ăa y est ...! Nous l'avons fait! Compilez votre lecteur:gcc -o tutorial07 tutorial07.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`
et profitez de votre lecteur de film fait en moins de 1000 lignes de C!Bien sĂ»r, il y a beaucoup de choses qui peuvent ĂȘtre ajoutĂ©es ou amĂ©liorĂ©es.
Donc, nous avons un joueur qui fonctionne, mais bien sĂ»r, il n'est pas aussi bon qu'il pourrait l'ĂȘtre. Il serait possible de modifier le fichier et d'ajouter beaucoup de choses utiles:- Avouons-le, ce joueur est nul. La version de ffplay.c sur laquelle il est basĂ© est complĂštement obsolĂšte et, par consĂ©quent, ce didacticiel doit ĂȘtre entiĂšrement rĂ©visĂ©. Si vous souhaitez passer Ă des projets plus sĂ©rieux en utilisant les bibliothĂšques FFmpeg, je vous recommande fortement de vĂ©rifier la derniĂšre version de ffplay.c comme tĂąche suivante.
- La gestion des erreurs dans notre code est terrible et peut ĂȘtre implĂ©mentĂ©e beaucoup mieux.
- , , , . , paused , , . , , . av_read_play. - , . , , . : , ffplay.c.
- .
- . , , , , VOB-.
- . , .
- . .
- , , , , YUV, time_base.
- .
- --, ; ffplay.c .
Si vous voulez en savoir plus sur FFmpeg, alors nous avons considĂ©rĂ© loin de tout. L'Ă©tape suivante consiste Ă Ă©tudier le codage multimĂ©dia. Il est prĂ©fĂ©rable de commencer avec le fichier output_example.c , que vous trouverez dans la distribution FFmpeg. Je pourrais dĂ©jĂ Ă©crire un autre manuel sur ce sujet, mais il est peu probable qu'il dĂ©passe ce guide.UPD.Il y a longtemps, je n'ai pas mis Ă jour ce texte, mais en attendant le monde ne reste pas immobile. Ce didacticiel ne nĂ©cessite que de simples mises Ă jour d'API; trĂšs peu de choses ont changĂ© en termes de concepts de base. La plupart de ces mises Ă jour ont en fait simplifiĂ© le code. Cependant, bien que j'aie parcouru le code et l'ai mis Ă jour, FFplay est toujours supĂ©rieur Ă ce joueur. La main sur le cĆur, nous l'admettons: dans ces leçons, nous avons Ă©crit un lecteur de film assez moche. Par consĂ©quent, si aujourd'hui (ou Ă l'avenir) vous souhaitez amĂ©liorer ce didacticiel, je vous recommande de vous familiariser avec FFplay et de dĂ©couvrir ce qui manque. Je pense que cela concerne principalement l'utilisation de l'Ă©quipement vidĂ©o, mais il est fort possible que je passe Ă cĂŽtĂ© d'autres Ă©lĂ©ments Ă©vidents. Peut-ĂȘtre qu'une comparaison avec le FFplay actuel conduirait Ă une réécriture radicale de certaines choses - je ne l'ai pas encore regardĂ©.Mais je suis trĂšs fier qu'au fil des ans, mon travail ait beaucoup aidĂ©, mĂȘme en tenant compte du fait que les gens recherchaient souvent du code ailleurs. Je suis extrĂȘmement reconnaissant Ă Chelyaev , qui s'est chargĂ© de remplacer toutes les fonctions obsolĂštes depuis que j'ai Ă©crit cette monographie il y a 8 (!) Ans.Je me rĂ©jouis en espĂ©rant que ces leçons se sont avĂ©rĂ©es utiles et non ennuyeuses. S'il y a des suggestions, des erreurs, des plaintes, des remerciements, etc. concernant ce guide, veuillez m'Ă©crire Ă dranger dog gmail dot com. Et oui, cela n'a aucun sens de me demander de vous aider avec votre projet FFmpeg. Il y a trop de lettres similaires .
Annexe 1. Liste des fonctions â â â
int avformat_open_input(AVFormatContext **ptr, const char * filename, AVInputFormat *fmt, AVDictionary **options)
Ouvre le nom du fichier multimédia, enregistre le contexte de format à l'adresse spécifiée dans ptr .fmt : sinon NULL, définit le format de fichier.buf_size : taille du tampon (facultatif).options : AVDictionary est rempli avec les paramÚtres de AVFormatContext et du démultiplexeur.void avformat_close_input(AVFormatContext **s)
Ferme le fichier multimédia. Cependant, il ne ferme pas les codecs.nt avio_open2 (AVIOContext **s, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options)
CrĂ©e un contexte d'E / S pour utiliser la ressource spĂ©cifiĂ©e dans l' url .s : pointeur vers l'endroit oĂč l' AVIOContext sera créé . En cas d'Ă©chec, la valeur spĂ©cifiĂ©e est dĂ©finie sur NULL.url : nom de la ressource Ă accĂ©der.flags : contrĂŽle l'ouverture de la ressource spĂ©cifiĂ©e dans l' url .int_cb : interruption du rappel pour une utilisation au niveau du protocole.options : un dictionnaire rempli de paramĂštres de protocole privĂ©s. Lorsque la fonction revient, le paramĂštre sera dĂ©truit et remplacĂ© par un dict contenant des options non trouvĂ©es. Peut ĂȘtre NULL.int av_dup_packet(AVPacket *pkt)
Bien sûr, c'est un hack: si ce package n'a pas été alloué, nous le postons ici. Renvoie 0 en cas de succÚs ou AVERROR_NOMEM en cas d'échec.int av_find_stream_info(AVFormatContext *s, AVDictionary **options)
Cette fonction recherche des informations de flux non Ă©videntes, telles que la frĂ©quence d'images. Ceci est utile pour les formats de fichiers sans en-tĂȘte tels que MPEG. Il est recommandĂ© d'appeler aprĂšs avoir ouvert le fichier. Retourne> = 0 en cas de succĂšs, AVERROR_ * en cas d'erreur.AVFrame *avcodec_free_frame()
Ancien nom pour av_frame_free. Modifié en lavc 55.28.1.void av_frame_free (AVFrame **frame)
LibÚre un cadre et tout objet alloué dynamiquement, par exemple, Extended_data.void av_free(void *ptr)
LibÚre la mémoire allouée à l'aide de av_malloc () ou av_realloc (). Vous pouvez appeler cette fonction avec ptr == NULL. Il est recommandé d'appeler à la place av_freep ().void av_freep(void *ptr)
LibÚre de la mémoire et définit le pointeur sur NULL. Utilise en interne av_free ().void av_free_packet(AVPacket *pkt)
Enrouler autour de la méthode de destruction de paquets (pkt-> destruct).int64_t av_gettime()
Obtenez l'heure actuelle en microsecondes.void av_init_packet(AVPacket *pkt)
Initialisation des champs de package facultatifs.void *av_malloc(unsigned int size)
Taille d'octet d'allocation de mémoire avec alignement adapté à tous les accÚs à la mémoire (y compris les vecteurs, si disponibles sur le CPU). av_malloc (0) doit renvoyer un pointeur non nul.void *av_mallocz(unsigned int size)
Identique à av_malloc (), mais initialise la mémoire à zéro.double av_q2d(AVRational a)
Double AVRational.int av_read_frame(AVFormatContext *s, AVPacket *pkt)
Renvoie la trame de flux suivante. Les informations sont stockĂ©es sous forme de package dans pkt.Le paquet retournĂ© est valide jusqu'au prochain av_read_frame () ou jusqu'Ă av_close_input_file () et doit ĂȘtre libĂ©rĂ© en utilisant av_free_packet. Pour un package vidĂ©o contient exactement une image. Pour l'audio, il contient un nombre entier de trames si chaque trame a une taille fixe connue (par exemple, des donnĂ©es PCM ou ADPCM). Si les images audio sont de taille variable (par exemple, audio MPEG), elles contiennent une image.pkt-> pts, pkt-> dts et pkt-> duration sont toujours dĂ©finis sur les valeurs correctes en unitĂ©s de AVStream.timebase (et il est supposĂ© que le format ne peut pas les fournir). pkt-> pts peut ĂȘtre AV_NOPTS_VALUE si le format vidĂ©o a des images B, il est donc prĂ©fĂ©rable de s'appuyer sur pkt-> dts si vous ne dĂ©ballez pas la charge utile.RĂ©sultat retournĂ©: 0, si tout va bien, <0, s'il y a une erreur ou la fin du fichier.void av_register_all();
Enregistre tous les codecs de la bibliothĂšque.int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)
Renvoie a * bq / cq .int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
Recherche une image clé à un horodatage.stream_index : si stream_index est -1, le flux par défaut est sélectionné et l'horodatage est automatiquement converti des unités AV_TIME_BASE en une base de temps spécifique au flux.horodatage : horodatage mesuré en unités de AVStream.time_base ou, si aucun flux n'est spécifié, puis en unités de AV_TIME_BASE.drapeaux : définissez les paramÚtres concernant la direction et le mode de recherche:AVSEEK_FLAG_ANY: recherchez dans n'importe quelle image, pas seulement les images clés.AVSEEK_FLAG_BACKWARD: recherche dans la direction opposée.AVSEEK_FLAG_BYTE: recherche basée sur la position en octets.AVFrame *avcodec_alloc_frame()
Ancien nom pour av_frame_alloc. Modifié en lavc 55.28.1.AVFrame *av_frame_alloc()
SĂ©lectionne un AVFrame et l'initialise. Peut ĂȘtre libĂ©rĂ© en utilisant av_frame_free ().int avcodec_decode_audio4(AVCodecContext *avctx, AVFrame *frame, int *got_frame_ptr, const AVPacket *avpkt)
DĂ©code une image audio d'avpkt en image. La fonction avcodec_decode_audio4 () dĂ©code un fichier audio d'AVPacket. Pour son dĂ©codage, un codec audio est utilisĂ©, qui Ă©tait associĂ© Ă avctx en utilisant avcodec_open2 (). La trame dĂ©codĂ©e rĂ©sultante est stockĂ©e dans l'AVFrame spĂ©cifiĂ©. Si la trame a Ă©tĂ© dĂ©compressĂ©e, elle mettra got_frame_ptr Ă 1.Avertissement: le tampon d'entrĂ©e, avpkt-> data, doit ĂȘtre FF_INPUT_BUFFER_PADDING_SIZE plus grand que les octets de lecture rĂ©els, car certains lecteurs de flux binaires optimisĂ©s lisent 32 ou 64 bits Ă la fois et peuvent lire jusqu'Ă la fin.avctx : contexte du codec.frame : frame cible.got_frame_ptr : target int, qui sera dĂ©fini si le cadre a Ă©tĂ© dĂ©compressĂ©.AVPKT: AVPacket contenant de l'audio.RĂ©sultat retournĂ©: si une erreur est retournĂ©e, une valeur nĂ©gative est retournĂ©e; sinon, le nombre d'octets utilisĂ©s Ă partir de l'AVPacket d'entrĂ©e est retournĂ©.int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *frameFinished, const AVPacket *avpkt)
DĂ©code une image vidĂ©o de buf en image. La fonction avcodec_decode_video2 () dĂ©code une image vidĂ©o Ă partir d'un tampon d'entrĂ©e de taille buf_size. Pour son dĂ©codage, un codec vidĂ©o est utilisĂ©, qui Ă©tait associĂ© Ă avctx en utilisant avcodec_open2 (). L'image dĂ©codĂ©e rĂ©sultante est enregistrĂ©e dans l'image.Avertissement: les exemples d'alignement et les problĂšmes de tampon qui s'appliquent Ă avcodec_decode_audio4 s'appliquent Ă©galement Ă cette fonction.avctx : contexte du codec.image : AVFrame dans lequel la vidĂ©o dĂ©codĂ©e sera enregistrĂ©e.frameFinished : zĂ©ro si aucune trame ne peut ĂȘtre dĂ©compressĂ©e, sinon elle n'est pas Ă©gale Ă zĂ©ro.avpkt: AVPacket d'entrĂ©e contenant le tampon d'entrĂ©e. Vous pouvez crĂ©er un tel package Ă l'aide de av_init_packet (), puis, aprĂšs avoir spĂ©cifiĂ© les donnĂ©es et la taille, certains dĂ©codeurs peuvent en outre avoir besoin d'autres champs, tels que drapeaux et AV_PKT_FLAG_KEY. Tous les dĂ©codeurs sont conçus pour utiliser le moins de champs possible.RĂ©sultat retournĂ©: En cas d'erreur, une valeur nĂ©gative est renvoyĂ©e, sinon le nombre d'octets est utilisĂ© ou zĂ©ro si aucune trame ne peut ĂȘtre dĂ©compressĂ©e.int64_t av_frame_get_best_effort_timestamp (const AVFrame *frame)
Une méthode d'accÚs simple pour obtenir best_effort_timestamp à partir d'un objet AVFrame.AVCodec *avcodec_find_decoder(enum CodecID id)
Recherche un dĂ©codeur avec CodecID. Renvoie NULL en cas d'erreur. Il doit ĂȘtre appelĂ© aprĂšs avoir obtenu l'AVCodecContext requis Ă partir du flux dans AVFormatContext Ă l'aide de codecCtx-> codec_id.void avcodec_flush_buffers(AVCodecContetx *avctx)
Rinçage du tampon. Appelé lors de la recherche ou du passage à un autre flux.AVCodecContext * avcodec_alloc_context3 (const AVCodec *codec)
Attribue AVCodecContext et définit ses champs sur des valeurs par défaut.int avcodec_copy_context (AVCodecContext *dest, const AVCodecContext *src)
Copiez les paramĂštres de l'AVCodecContext source dans l'AVCodecContext cible. Le contexte rĂ©sultant du codec de destination sera fermĂ©, c'est-Ă -dire vous devez appeler avcodec_open2 () avant d'utiliser cet AVCodecContext pour dĂ©coder / encoder des donnĂ©es vidĂ©o / audio.dest : doit ĂȘtre initialisĂ© avec avcodec_alloc_context3 (NULL), sinon il ne sera pas initialisĂ©.int avcodec_open2(AVCodecContext *avctx, AVCodec *codec, AVDictionary **options)
Initialise avctx pour utiliser le codec spĂ©cifiĂ© dans le codec . Doit ĂȘtre utilisĂ© aprĂšs avcodec_find_decoder. Retourne zĂ©ro en cas de succĂšs et une valeur nĂ©gative en cas d'erreur.int avpicture_fill(AVPicture *picture, uint8_t *ptr, int pix_fmt, int width, int height)
Définit la structure vers laquelle l'image pointe, avec le tampon ptr , le format pix_fmt et la largeur et la hauteur spécifiées. Renvoie la taille des données d'image en octets.int avpicture_get_size(int pix_fmt, int width, int height)
Calcule le nombre d'octets nécessaires pour une image d'une largeur, d'une hauteur et d'un format d'image donnés.struct SwsContext* sws_getContext(int srcW, int srcH, int srcFormat, int dstW, int dstH, int dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, double *param)
Renvoie SwsContext pour une utilisation dans sws_scale.srcW , srcH , srcFormat : largeur, hauteur et format des pixels souhaitĂ©s.dstW , dstH , dstFormat : largeur, hauteur et format des derniers pixels.flags : la mĂ©thode de mise Ă l'Ă©chelle Ă utiliser.Les options suivantes sont disponibles: SWS_FAST_BILINEAR, SWS_BILINEAR, SWS_BICUBIC, SWS_X, SWS_POINT, SWS_AREA, SWS_BICUBLIN, SWS_GAUSS, SWS_SINC, SWS_LANCZOS, SWS_SPLINE.Les autres indicateurs incluent les indicateurs de capacitĂ© du processeur: SWS_CPU_CAPS_MMX, SWS_CPU_CAPS_MMX2, SWS_CPU_CAPS_3DNOW, SWS_CPU_CAPS_ALTIVEC.D'autres indicateurs incluent (actuellement pas complĂštement implĂ©mentĂ©s) SWS_FULL_CHR_H_INT, SWS_FULL_CHR_H_INP et SWS_DIRECT_BGR.Enfin, il y a SWS_ACCURATE_RND et peut-ĂȘtre le plus utile pour les dĂ©butants, SWS_PRINT_INFO.Je n'ai aucune idĂ©e de ce que font la plupart d'entre eux. Peut-ĂȘtre m'Ă©cris-tu?srcFilter , dstFilter : SwsFilter pour la source et la destination. SwsFilter permet le filtrage couleur / luminositĂ©. La valeur par dĂ©faut est NULL.param : devrait ĂȘtre un pointeur vers un tampon int [2] avec des coefficients. Non documentĂ©. Il semble ĂȘtre utilisĂ© pour modifier lĂ©gĂšrement les algorithmes de mise Ă l'Ă©chelle standard. La valeur par dĂ©faut est NULL. Uniquement pour les experts!int sws_scale(SwsContext *c, uint8_t *src, int srcStride[], int srcSliceY, int srcSliceH, uint8_t dst[], int dstStride[]
sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, is->video_st->codec->height, pict.data, pict.linesize);
Met à l'échelle les
données en src selon nos paramÚtres dans notre SwsContext * c .srcStride et dstStride sont les tailles de ligne source et destination.SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param)
Ajoute une fonction de rappel qui s'exĂ©cute aprĂšs un nombre spĂ©cifiĂ© de millisecondes. La fonction de rappel transmet l'intervalle de temporisation en cours et le paramĂštre fourni par l'utilisateur Ă partir de l'appel SDL_AddTimer et renvoie l'intervalle de temporisation suivant. (Si la valeur de retour du rappel correspond Ă la valeur transmise, le minuteur continue de fonctionner Ă la mĂȘme vitesse.) Si la valeur de retour du rappel est 0, le minuteur est annulĂ©.Une autre façon d'annuler le temporisateur en cours consiste Ă appeler SDL_RemoveTimer avec l'identifiant du temporisateur (qui a Ă©tĂ© renvoyĂ© par SDL_AddTimer).La fonction de rappel du minuteur peut s'exĂ©cuter sur un thread diffĂ©rent de votre programme principal et ne doit donc pas appeler de fonction Ă partir d'elle-mĂȘme. Cependant, vous pouvez toujours appeler SDL_PushEvent.Le degrĂ© de dĂ©tail du temporisateur dĂ©pend de la plate-forme, mais vous devez vous attendre Ă au moins 10 ms, car il s'agit de la valeur la plus courante. Cela signifie que si vous demandez un temporisateur de 16 ms, le rappel commencera aprĂšs environ 20 ms sur un systĂšme non chargĂ©. Si vous devez dĂ©finir un indicateur qui signale la mise Ă jour des images Ă une vitesse de 30 images par seconde (toutes les 33 ms), vous pouvez dĂ©finir une minuterie de 30 ms (voir l'exemple ci-dessous). Si vous utilisez cette fonction, vous devez passer SDL_INIT_TIMER Ă SDL_Init.Renvoie la valeur d'identificateur du minuteur ajoutĂ©, ou NULL si une erreur se produit.Format de rappel:Uint32 callback ( Uint32, void * param)
int SDL_CondSignal(SDL_cond *cond)
Redémarrage de l'un des threads en attente de la variable de condition cond . Renvoie 0 en cas de succÚs et -1 en cas d'erreur.int SDL_CondWait(SDL_cond *cond, SDL_mutex *mut);
DĂ©verrouillez le mutex fourni et attendez qu'un autre thread appelle SDL_CondSignal ou SDL_CondBroadcast pour la variable de condition cond, puis verrouillez Ă nouveau le mutex. Le mutex doit ĂȘtre verrouillĂ© avant d'accĂ©der Ă cette fonction. Renvoie 0 lorsqu'un signal est reçu ou -1 en cas d'erreur.SDL_cond *SDL_CreateCond(void);
Crée une variable de condition.SDL_Thread *SDL_CreateThread(int (*fn)(void *), void *data);
SDL_CreateThread crée un nouveau thread d'exécution qui partage toute la mémoire globale de son parent, des gestionnaires de signaux, des descripteurs de fichiers, etc. Et il exécute la fonction fn , en lui passant les données du pointeur vide. Le thread se termine lorsque fn renvoie une valeur.void SDL_Delay (Uint32 );
Attend le nombre de millisecondes spĂ©cifiĂ©. SDL_Delay attendra au moins l'heure spĂ©cifiĂ©e, mais peut-ĂȘtre plus longtemps en raison de la planification du systĂšme d'exploitation.Remarque: attendez-vous Ă une granularitĂ© de retard d'au moins 10 ms. Certaines plateformes ont des mesures plus courtes, mais c'est l'option la plus courante.SDL_Overlay *SDL_CreateYUVOverlay(int width, int height, Uint32 format, SDL_Surface *display);
SDL_CreateYUVOverlay crĂ©e une superposition YUV de la largeur, de la hauteur et du format spĂ©cifiĂ©s (pour une liste des formats disponibles, voir la structure de donnĂ©es SDL_Overlay) pour l'affichage fourni. Renvoie SDL_Overlay.l'affichage devrait en fait ĂȘtre une surface dĂ©rivĂ©e de SDL_SetVideoMode, sinon cette fonction fonctionnera par dĂ©faut.Le terme «superposition» est incorrect, car si la superposition n'est pas créée dans le matĂ©riel, le contenu de la surface d'affichage sous la zone oĂč la superposition est affichĂ©e sera Ă©crasĂ© lorsque la superposition est affichĂ©e.int SDL_LockYUVOverlay(SDL_Overlay *overlay)
SDL_LockYUVOverlay bloque la superposition pour un accÚs direct aux données de pixels. Renvoie 0 en cas de succÚs ou -1 en cas d'erreur.void SDL_UnlockYUVOverlay(SDL_Overlay *overlay)
DĂ©verrouille une superposition prĂ©cĂ©demment verrouillĂ©e. La superposition doit ĂȘtre dĂ©verrouillĂ©e avant de pouvoir ĂȘtre affichĂ©e.int SDL_DisplayYUVOverlay(SDL_Overlay *overlay, SDL_Rect *dstrect)
Place la superposition sur la surface spécifiée lors de sa création. La structure SDL_Rect dstrect définit la position et la taille de la destination. Si dstrect est plus ou moins une superposition, alors la superposition sera mise à l'échelle, ceci est optimisé pour une mise à l'échelle 2x. Renvoie 0 en cas de succÚs.void SDL_FreeYUVOverlay(SDL_Overlay *overlay)
LibÚre la superposition créée par SDL_CreateYUVOverlay.int SDL_Init(Uint32 flags);
Initialise le SDL. Cela doit ĂȘtre appelĂ© avant toutes les autres fonctions SDL. Le paramĂštre flags spĂ©cifie les parties du SDL Ă initialiser.SDL_INIT_TIMER - initialise le sous-systĂšme de temporisation.SDL_INIT_AUDIO - initialise le sous-systĂšme audio.SDL_INIT_VIDEO - initialise le sous-systĂšme vidĂ©o.SDL_INIT_CDROM - initialise le sous-systĂšme du CD-ROM.SDL_INIT_JOYSTICK - initialise le sous-systĂšme du joystick.SDL_INIT_EVERYTHING - Initialise tout ce qui prĂ©cĂšde.SDL_INIT_NOPARACHUTE - ne permet pas Ă SDL de dĂ©tecter les erreurs fatales.SDL_INIT_EVENTTHREAD - lance le gestionnaire d'Ă©vĂ©nements dans un thread sĂ©parĂ©.Renvoie -1 en cas d'erreur ou 0 en cas de succĂšs. Vous pouvez obtenir un message d'erreur Ă©tendu en appelant SDL_GetError. Une cause typique d'une erreur est l'utilisation d'un affichage spĂ©cifique sans le support correspondant pour le sous-systĂšme, par exemple, l'absence d'un pilote de souris lors de l'utilisation d'un tampon de trame avec le pĂ©riphĂ©rique. Dans ce cas, vous pouvez soit compiler SDL sans souris, soit dĂ©finir la variable d'environnement "SDL_NOMOUSE = 1" avant de dĂ©marrer l'application.SDL_mutex *SDL_CreateMutex(void);
Crée un nouveau mutex déverrouillé.int SDL_LockMutex(SDL_mutex *mutex)
SDL_LockMutex est un alias pour SDL_mutexP. Il bloque un mutex créé prĂ©cĂ©demment Ă l'aide de SDL_CreateMutex. Si le mutex est dĂ©jĂ bloquĂ© par un autre thread, alors SDL_mutexP ne renvoie pas de valeur tant que le thread bloquĂ© par lui ne le dĂ©verrouille pas (Ă l'aide de SDL_mutexV). Lorsque le mutex est appelĂ© Ă nouveau, SDL_mutexV (alias SDL_UnlockMutex) doit ĂȘtre appelĂ© un nombre Ă©gal de fois pour remettre le mutex dans un Ă©tat dĂ©verrouillĂ©. Renvoie 0 en cas de succĂšs ou -1 en cas d'erreur.int SDL_UnlockMutex(SDL_Mutex *mutex)
Déverrouillage Mutex.int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)
Cette fonction ouvre l'unitĂ© audio avec les paramĂštres requis et renvoie 0 en cas de succĂšs, en plaçant les paramĂštres matĂ©riels rĂ©els dans la structure vers laquelle elle pointe finalement. Si une valeur NULL est reçue, les donnĂ©es audio transmises Ă la fonction de rappel seront garanties d'avoir le format requis et, si nĂ©cessaire, seront automatiquement converties au format audio matĂ©riel. Cette fonction renvoie -1 si le pĂ©riphĂ©rique audio n'a pas pu ĂȘtre ouvert ou le flux audio n'a pas pu ĂȘtre configurĂ©.Pour ouvrir un pĂ©riphĂ©rique audio, vous devez crĂ©er le SDL_AudioSpec souhaitĂ©. Ensuite, vous devez remplir cette structure avec les spĂ©cifications audio souhaitĂ©es.desire-> freq : frĂ©quence sonore souhaitĂ©e en Ă©chantillons par seconde.dĂ©sirĂ©-> format : format audio souhaitĂ© (voir SDL_AudioSpec).souhaitĂ©-> canaux: Canaux requis (1 pour mono, 2 pour stĂ©rĂ©o, 4 pour son surround, 6 pour son surround avec centrage et LFE).dĂ©sirĂ©-> Ă©chantillons : taille de tampon audio souhaitĂ©e dans les Ă©chantillons. Ce nombre doit ĂȘtre une puissance de deux et peut ĂȘtre ajustĂ© par le pilote audio Ă une valeur plus adaptĂ©e au matĂ©riel. Les valeurs optimales vont de 512 Ă 8192 inclus, selon l'application et la vitesse du processeur. Des valeurs plus petites entraĂźnent des temps de rĂ©ponse plus rapides, mais peuvent entraĂźner de mauvaises performances si l'application effectue un traitement intensif et ne peut pas remplir le tampon audio Ă temps. L'Ă©chantillon stĂ©rĂ©o se compose des canaux droit et gauche dans l'ordre LR. Veuillez noter que le nombre d'Ă©chantillons est directement liĂ© au temps en utilisant la formule suivante: ms = (Ă©chantillons * 1000) / freq .dĂ©sirĂ©-> rappel : doit ĂȘtre rĂ©glĂ© sur une fonction qui sera appelĂ©e lorsque l'unitĂ© audio sera prĂȘte Ă recevoir des donnĂ©es supplĂ©mentaires. Le pointeur vers le tampon audio et la longueur en octets du tampon audio sont transmis. Cette fonction est gĂ©nĂ©ralement exĂ©cutĂ©e dans un thread sĂ©parĂ©, et il est donc nĂ©cessaire de protĂ©ger les structures de donnĂ©es auxquelles elle accĂšde en appelant SDL_LockAudio et SDL_UnlockAudio dans le code. Le prototype de rappel est un rappel vide ( void * userdata , Uint8 * stream , int len ) . userdata - un pointeur stockĂ© dans le champ userdata SDL_AudioSpec. courantEst un pointeur vers le tampon audio que vous souhaitez remplir avec des informations, et len est la longueur du tampon audio en octets.required-> userdata : ce pointeur est passĂ© comme premier paramĂštre Ă la fonction de rappel.SDL_OpenAudio lit ces champs Ă partir de la structure SDL_AudioSpec souhaitĂ©e transmise Ă la fonction et essaie de trouver la configuration audio qui correspond Ă votre dĂ©sir. Comme mentionnĂ© ci-dessus, si le paramĂštre rĂ©sultant est NULL, alors SDL est converti des paramĂštres sonores souhaitĂ©s en paramĂštres d'Ă©quipement pendant la lecture.Si NULL est renvoyĂ©, alors la SDL_AudioSpec requise est votre spĂ©cification de travail, sinon la SDL_AudioSpec rĂ©sultante devient une spĂ©cification de travail et la spĂ©cification souhaitĂ©e peut ĂȘtre supprimĂ©e. Les donnĂ©es de la spĂ©cification de travail sont utilisĂ©es lors de la construction de SDL_AudioCVT pour convertir les donnĂ©es tĂ©lĂ©chargĂ©es au format d'Ă©quipement.SDL_OpenAudio calcule les champs de taille et de silence pour les spĂ©cifications souhaitĂ©es et rĂ©sultantes. Le champ taille stocke la taille totale du tampon audio en octets, tandis que silence stocke la valeur utilisĂ©e pour reprĂ©senter le silence dans le tampon audioLe pĂ©riphĂ©rique audio commence Ă lire le silence lorsqu'il est ouvert et doit ĂȘtre allumĂ© pour la lecture en appelant SDL_PauseAudio (0) lorsque vous ĂȘtes prĂȘt Ă appeler la fonction de rappel audio. Ătant donnĂ© que le pilote audio peut modifier la taille demandĂ©e du tampon audio, vous devez sĂ©lectionner les tampons de mixage locaux aprĂšs avoir ouvert le pĂ©riphĂ©rique audio.void SDL_PauseAudio(int pause_on)
Cette fonction met en pause et arrĂȘte le traitement du rappel audio. Il doit ĂȘtre appelĂ© avec pause_on = 0 aprĂšs avoir ouvert le pĂ©riphĂ©rique audio afin de commencer la lecture du son. Cela vous permet d'initialiser en toute sĂ©curitĂ© les donnĂ©es de la fonction de rappel aprĂšs avoir ouvert l'unitĂ© audio. Le silence sera enregistrĂ© sur l'appareil audio pendant une pause.int SDL_PushEvent(SDL_Event *event)
Une file d'attente d'Ă©vĂ©nements qui est rĂ©ellement utilisĂ©e comme canal de communication bidirectionnel. Non seulement les Ă©vĂ©nements peuvent ĂȘtre lus dans la file d'attente, mais l'utilisateur peut Ă©galement y placer leurs propres Ă©vĂ©nements. Un Ă©vĂ©nement est un pointeur sur la structure de l'Ă©vĂ©nement que vous souhaitez mettre en file d'attente. L'Ă©vĂ©nement est copiĂ© dans la file d'attente et l'appelant peut gĂ©rer la mĂ©moire pointĂ©e aprĂšs avoir renvoyĂ© SDL_PushEvent. Cette fonction est orientĂ©e thread et peut ĂȘtre appelĂ©e en toute sĂ©curitĂ© Ă partir d'autres threads. Renvoie 0 en cas de succĂšs ou -1 si l'Ă©vĂ©nement n'a pas pu ĂȘtre distribuĂ©.int SDL_WaitEvent(SDL_Event *event)
Attend indéfiniment le prochain événement disponible, renvoyant 0 si une erreur s'est produite lors de l'attente des événements, 1 sinon. Si l'événement n'est pas NULL, l'événement suivant est supprimé de la file d'attente et stocké dans cette zone.void SDL_Quit()
DĂ©sactive tous les sous-systĂšmes SDL et libĂšre les ressources qui lui sont allouĂ©es. Cela doit toujours ĂȘtre appelĂ© avant de quitter.SDL_Surface *SDL_SetVideoMode(int width, int height, int bitsperpixel, Uint32 flags)
RĂ©glage du mode vidĂ©o avec la largeur, la hauteur et les bits de pixel spĂ©cifiĂ©s. Ă partir de SDL 1.2.10, si la largeur et la hauteur sont Ă©gales Ă 0, il utilisera la largeur et la hauteur du mode vidĂ©o actuel (ou le mode bureau si le mode n'est pas dĂ©fini). Si bitsperpixel vaut 0, il est traitĂ© comme les bits d'affichage actuels par pixel. Le paramĂštre flags est le mĂȘme que le champ flags de la structure SDL_Surface. Ou une combinaison des valeurs suivantes:SDL_SWSURFACE - crĂ©e une surface vidĂ©o dans la mĂ©moire systĂšme.SDL_HWSURFACE - crĂ©e une surface vidĂ©o dans la mĂ©moire vidĂ©o.SDL_ASYNCBLIT - permet l'utilisation de mises Ă jour asynchrones de la surface d'affichage. Cela ralentit gĂ©nĂ©ralement le travail sur les ordinateurs Ă processeur unique, mais peut augmenter la vitesse des systĂšmes SMP.SDL_ANYFORMAT - gĂ©nĂ©ralement, si une surface vidĂ©o avec les bits demandĂ©s par pixel (bpp - Ă partir de bits par pixel) n'est pas disponible, SDL Ă©mule la vidĂ©o avec une surface ombrĂ©e. Passer SDL_ANYFORMAT empĂȘche cela et force le SDL Ă utiliser la surface de la vidĂ©o, quelle que soit sa profondeur en pixels.SDL_HWPALETTE - Fournit un accĂšs exclusif SDL Ă la palette. Sans cet indicateur, vous ne pouvez pas toujours obtenir les couleurs que vous demandez Ă l'aide de SDL_SetColors ou SDL_SetPalette.SDL_DOUBLEBUF - active le double tampon matĂ©riel; valable uniquement avec SDL_HWSURFACE. Un appel Ă SDL_Flip inversera les tampons et rafraĂźchira l'Ă©cran. Tout le dessin aura lieu sur une surface qui n'est pas actuellement affichĂ©e. Si la double mise en mĂ©moire tampon ne peut pas ĂȘtre activĂ©e, alors SDL_Flip exĂ©cutera simplement SDL_UpdateRect en plein Ă©cran.SDL_FULLSCREEN SDL - essayez d'utiliser le mode plein Ă©cran. Si la modification de la rĂ©solution matĂ©rielle n'est pas possible (pour une raison quelconque), la rĂ©solution supĂ©rieure suivante sera utilisĂ©e et la fenĂȘtre d'affichage sera centrĂ©e sur un fond noir.SDL_OPENGL - crĂ©e un contexte de rendu OpenGL. Il est supposĂ© que les attributs vidĂ©o OpenGL avec SDL_GL_SetAttribute sont prĂ©dĂ©finis.SDL_OPENGLBLIT - crĂ©e un contexte de rendu OpenGL, comme dĂ©crit ci-dessus, mais autorise les opĂ©rations de blitting normales. Une surface d'Ă©cran (2D) peut avoir un canal alpha et SDL_UpdateRects doit ĂȘtre utilisĂ© pour mettre Ă jour les changements de surface d'Ă©cran. REMARQUE. Cette option est enregistrĂ©e uniquement pour des raisons de compatibilitĂ© et sera supprimĂ©e dans les futures versions. Non recommandĂ© pour une utilisation dans un nouveau code.SDL_RESIZABL -crĂ©er une fenĂȘtre redimensionnable. Lorsque la taille de la fenĂȘtre est modifiĂ©e par l'utilisateur, l'Ă©vĂ©nement SDL_VIDEORESIZE est gĂ©nĂ©rĂ© et SDL_SetVideoMode peut ĂȘtre appelĂ© Ă nouveau avec une nouvelle taille.SDL_NOFRAME Si possible, SDL_NOFRAME force SDL Ă crĂ©er une fenĂȘtre sans titre ni encadrĂ©e. Ce drapeau est automatiquement dĂ©fini en mode plein Ă©cran.Remarque. Peu importe quels indicateurs SDL_SetVideoMode peuvent satisfaire, ils sont dĂ©finis dans l'Ă©lĂ©ment flags de la surface renvoyĂ©e.REMARQUE. Le pixel bit 24 utilise une reprĂ©sentation compactĂ©e de 3 octets par pixel. Pour le mode 4 octets par pixel le plus courant, utilisez un pixel 32 bits. Curieusement, 15 et 16 demanderont un mode 2 octets par pixel, mais avec des formats de pixels diffĂ©rents.REMARQUE. Utilisez SDL_SWSURFACE si vous prĂ©voyez d'effectuer des manipulations de pixels distinctes ou de faire glisser des surfaces Ă l'aide de canaux alpha et que vous avez besoin d'une frĂ©quence d'images Ă©levĂ©e. Lorsque vous utilisez des surfaces matĂ©rielles (SDL_HWSURFACE), le SDL copie les surfaces de la mĂ©moire vidĂ©o dans la mĂ©moire systĂšme lorsque vous les verrouillez et inversement lorsque vous les dĂ©verrouillez. Cela peut entraĂźner une baisse significative des performances. (Gardez Ă l'esprit que vous pouvez rechercher une surface matĂ©rielle mais toujours obtenir une surface logicielle. De nombreuses plates-formes ne peuvent fournir une surface matĂ©rielle que lorsque vous utilisez SDL_FULLSCREEN.) SDL_HWSURFACE est mieux utilisĂ© lorsque les surfaces que vous clignoterez peuvent Ă©galement ĂȘtre stockĂ©es dans la mĂ©moire vidĂ©o.REMARQUE. Si vous souhaitez contrĂŽler la position sur l'Ă©cran lors de la crĂ©ation de la surface de la fenĂȘtre, vous pouvez le faire en dĂ©finissant les variables d'environnement «SDL_VIDEO_CENTERED = center» ou «SDL_VIDEO_WINDOW_POS = x, y». Vous pouvez les installer via SDL_putenv.Valeur de retour: surface du tampon de trame ou NULL en cas d'Ă©chec. La surface retournĂ©e est libĂ©rĂ©e par SDL_Quit et ne doit pas ĂȘtre libĂ©rĂ©e par l'appelant.REMARQUE. Cette rĂšgle inclut les appels consĂ©cutifs Ă SDL_SetVideoMode (c'est-Ă -dire le redimensionnement) - la surface existante sera libĂ©rĂ©e automatiquement.
Annexe 2. Structures de donnĂ©es â â â
AVCodecContext
Toutes les informations sur le codec du flux, de AVStream-> codec. Quelques attributs importants:AVRational time_base : nombre d'images par secondeint sample_rate : échantillons par secondeint canal : nombre de canauxVoir la liste complÚte (trÚs impressionnante) ici ( archives web, car le lien d'origine est déjà inexistant ). De nombreux paramÚtres sont utilisés principalement pour le codage, pas pour le décodage.AVFormatContext
Champs de donnĂ©es:const AVClass * av_classAVInputFormat * iformatAVOutputFormat * oformatvoid * priv_data :ByteIOContext pb : utilisĂ© pour la manipulation de fichiers de bas niveau.unsigned int nb_streams : nombre de threads dans le fichier.Flux AVStream * [MAX_STREAMS] : les donnĂ©es de chaque flux sont stockĂ©es ici.char nom de fichier [1024]: mais qu'en est-il sans (dans l'original - duh ).Informations sur le fichier: horodatageint64_t : char title [512]:ombles auteur [512]:omble chevalier droit d' auteur [512]:omble chevalier commentaire [512]:ombles album [512]:int annĂ©e :int piste :ombles genre [32]:int ctx_flags : Lesvaleurs possibles sont AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS, AVFMT_RAWPICTURE, AVFMT_GLOBALHEADER, AVFMT_NOTIMESTAMPS, AVFMT_GENERIC_INDEXAVPacketList * packet_buffer : ce tampon est nĂ©cessaire uniquement lorsque les paquets sont dĂ©jĂ tamponnĂ©s mais non dĂ©codĂ©s, par exemple, pour recevoir les paramĂštres du codec dans les flux mpeg.int64_tstart_time : lors du dĂ©codage: la position de la premiĂšre trame du composant, en fractions de seconde, AV_TIME_BASE. Ne dĂ©finissez JAMAIS cette valeur directement: elle est dĂ©duite des valeurs d'AVStream.int64_t durĂ©e: dĂ©codage : durĂ©e du flux, en fractions d'AV_TIME_BASE. Ne dĂ©finissez JAMAIS cette valeur directement: elle est dĂ©duite des valeurs d'AVStream.int64_t file_size : taille totale du fichier, 0 si inconnu.int bit_rate : decoding: dĂ©bit binaire total du flux en bit / s, 0 s'il n'est pas disponible. NE le dĂ©finissez JAMAIS directement si file_size et la durĂ©e connue dans ffmpeg peuvent le calculer automatiquement.AVStream * cur_stconst uint8_t * cur_ptrint cur_lenAVPacket cur_pkt :int64_t data_offset :int index_built : dĂ©calage du premier paquet.int mux_rate :int packet_size :int preload :int max_delay :int loop_output : nombre de boucles de sortie dans les formats pris en charge.drapeaux int :int loop_input :unsigned int probesize : dĂ©codage: taille des donnĂ©es de l'Ă©chantillon; non utilisĂ© dans le codage.int max_analyze_duration : durĂ©e maximale en unitĂ©s de AV_TIME_BASE pendant laquelle les donnĂ©es d'entrĂ©e doivent ĂȘtre analysĂ©es dans av_find_stream_info ()const uint8_t * clĂ© :int keylen :AVIOContext
Contexte d'E / S pour accĂ©der aux ressources.const AVClass * av_class : classe pour les paramĂštres privĂ©s.unsigned char * buffer : dĂ©but du tampon.int buffer_size : taille maximale du tampon.unsigned char * buf_ptr : position actuelle dans le tampon.unsigned char * buf_end : Les donnĂ©es peuvent ĂȘtre plus petites que buffer + buffer_size si la fonction de lecture a retournĂ© moins de donnĂ©es que demandĂ©, par exemple.void * opaque : pointeur privĂ© passĂ© Ă read / write / search / ...int (* read_packet) (void * opaque, uint8_t * buf, int buf_size) :int (* write_packet) (void * opaque, uint8_t * buf, int buf_size ) :int64_t (* cherche) (void * opaque, int64_t offset, int d'oĂč) :int64_t pos : position dans le fichier du tampon courant.int must_flush : true si la prochaine recherche doit ĂȘtre rĂ©initialisĂ©e.int eof_reached : true si la fin du fichier est atteinte.int write_flag : true si ouvert pour l'Ă©criture.int max_packet_size :unsigned long checksum :unsigned char * checksum_ptr :unsigned long (* update_checksum) (unsigned long checksum, const uint8_t * buf, unsigned int size) :int error : contient le code d'erreur ou 0 si aucune erreur ne s'est produite.int (* read_pause) (void * opaque, int pause): mettre en pause ou reprendre la lecture pour les protocoles de streaming rĂ©seau, par exemple.int64_t (* read_seek) (void * opaque, int stream_index, int64_t timestamp, int flags) : recherchez l'horodatage spĂ©cifiĂ© dans le flux avec l'index stream_index spĂ©cifiĂ©.int recherche : combinaison des drapeaux AVIO_SEEKABLE_ ou 0 lorsque le flux n'est pas consultable.int64_t maxsize : taille maximale du fichier utilisĂ©e pour limiter la sĂ©lection. Ce champ est interne Ă libavformat et son accĂšs depuis l'extĂ©rieur est interdit.int direct : avio_read et avio_write doivent ĂȘtre exĂ©cutĂ©s directement dans la mesure du possible, et ne pas passer par le tampon, et avio_seek appellera toujours directement la fonction de recherche principale.int64_t bytes_read: statistiques de lecture d'octets Ce champ est interne Ă libavformat et l'accĂšs externe est refusĂ©.int seek_count : statistiques de recherche. Ce champ est interne Ă libavformat et son accĂšs depuis l'extĂ©rieur est interdit.int writeout_count : Ă©crire des statistiques. Ce champ est interne Ă libavformat et son accĂšs depuis l'extĂ©rieur est interdit.int orig_buffer_size : la taille du tampon d'origine utilisĂ©e en interne aprĂšs vĂ©rification et retour pour rĂ©initialiser la taille du tampon. Ce champ est interne Ă libavformat et son accĂšs depuis l'extĂ©rieur est interdit.AVDictionary
Utilisé pour passer des paramÚtres à ffmpeg.nombre entier : AVDictionaryEntry * elems :AVDictionaryEntry
Utilisé pour stocker les entrées du dictionnaire dans AVDictionary.char * ket :char * valeur :AVFrame
Cette structure dépend du type de codec et est donc déterminée dynamiquement. Cependant, il existe des propriétés et des méthodes communes pour cette structure:uint8_t * data [4] :int linesize [4] : information stride.uint8_t * base [4] :int key_frame :int pict_type :int64_t pts : ce ne sont pas les pts attendus lors du décodage.int coded_picture_number :int display_picture_number :int qualité :int age :int référence :int8_t * qscale_table :int qstride :uint8_t * mbskip_table :int16_t (* motion_val [2]) [2]:uint32_t * mb_type :uint8_t motion_subsample_log2 :void * opaque : données utilisateuruint64_t erreur [4] :int type :int repeat_pict : vous demande de répéter l'image un nombre spécifié de fois.int qscale_type :int interlaced_frame :int top_field_first :AVPanScan * pan_scan :int palette_has_changed :int buffer_hints :short * dct_coeff :int8_t * ref_index [2] :AVPacket
Structure dans laquelle les donnĂ©es brutes des paquets sont stockĂ©es. Ces donnĂ©es doivent ĂȘtre transfĂ©rĂ©es vers avcodec_decode_audio2 ou avcodec_decode_video pour recevoir une trame.int64_t pts : horodatage de prĂ©sentation en unitĂ©s de base de temps.int64_t dts : horodatage de la dĂ©compression en unitĂ©s de time_base.uint8_t * data : donnĂ©es brutes.int size : taille des donnĂ©es.int stream_index : flux dont AVPacket est issu, basĂ© sur la quantitĂ© dans AVFormatContext.drapeaux int : PKT_FLAG_KEY est dĂ©fini si le paquet est une image clĂ©.int duration : durĂ©e de la prĂ©sentation en unitĂ©s de time_base (0 si non disponible)void (* destruct) (struct AVPacket *) : fonction de libĂ©ration des ressources pour ce package (av_destruct_packet par dĂ©faut).void * priv :int64_t pos : position d'octet dans le flux, -1 si inconnu.AVPacketList
Une simple liste chaßnée pour les packages.AVPacket pkt :AVPacketList * suivant :AVPicture
Cette structure est exactement la mĂȘme que les deux premiers Ă©lĂ©ments de donnĂ©es AVFrame, elle est donc souvent supprimĂ©e. Couramment utilisĂ© dans les fonctions SWS.uint8_t * data [4] :int linesize [4] : le nombre d'octets dans la chaĂźne.AVRational
Structure simple pour représenter des nombres rationnels.int num : numérateur.int den : dénominateur.AVStream
La structure du flux. Vous utiliserez probablement ces informations dans le codec le plus souvent.int index :int id :AVCodecContext * codec :AVRational r_frame_rate :void * priv_data :int64_t codec_info_duration :int codec_info_nb_frames :AVFrac pts :AVRational time_base :int pts_wrap_bits :int stream_copy :un disque ne peut pas ignorer le paquet en dĂ©multiplexage.float quality :int64_t start_time :int64_t duration:Langue Char [4] :int need_parsing : 1 -> besoin parsing complĂšte, 2 -> parse tĂȘtes uniquement, sans remballerAVCodecParserContext * analyseur :int64_t cur_dts :int last_IP_duration :int64_t last_IP_pts :AVIndexEntry * index_entries :int nb_index_entries :un unsigned int index_entries_allocated_size :int64_t nb_frames : nombre de trames dans ce flux (si connu) ou 0int64_t pts_buffer [MAX_REORDER_DELAY + 1] :ByteIOContext
Une structure qui stocke des informations de bas niveau sur un fichier vidĂ©o.unsigned char * buffer :int buffer_size :unsigned char * buf_ptr :unsigned char * buf_end :void * opaque :int (* read_packet) (void * opaque, uint8_t * buf, int buf_size) :int (* write_packet) (void * opaque, uint8_t * buf, int buf_size) :offset_t (* cherche) (void * opaque, offset_t offset, int d'oĂč) :offset_t pos :int must_flush :int eof_reached :int write_flag :int is_streamed :int max_packet_size :unsigned long checksum :unsigned char * checksum_ptr :unsigned long (* update_checksum) (unsigned long checksum:
const uint8_t * buf, unsigned int size) :int error : contient le code d'erreur ou 0 si aucune erreur ne s'est produite.SDL_AudioSpec
UtilisĂ© pour dĂ©crire le format de certaines donnĂ©es audio.freq : frĂ©quence sonore en Ă©chantillons par seconde.format : format de donnĂ©es audio.canaux : nombre de canaux: 1 - mono, 2 - stĂ©rĂ©o, 4 surround, 6 surround avec centrage etsilence LFE : valeur de silence du tampon sonore (calculĂ©e).samples : taille du buffer audio dans les samples.taille : la taille du tampon audio en octets (calculĂ©e).callback (..) : fonction de rappel pour remplir le buffer audio.userdata : un pointeur vers les donnĂ©es utilisateur qui sont passĂ©es Ă la fonction de rappel.Les valeurs de format suivantes sontvalides : AUDIO_U8 - ââĂchantillons non signĂ©s 8 bits.AUDIO_S8 - Ă©chantillons 8 bits signĂ©s.AUDIO_U16 ou AUDIO_U16LSB - non pris en charge par tout le matĂ©riel (ordre des octets faibles 16 bits non signĂ©).AUDIO_S16 ou AUDIO_S16LS - non pris en charge par tous les matĂ©riels (16 bits avec ancien ordre d'octets)AUDIO_U16MSB - non pris en charge par tous les matĂ©riels (big-endian 16 bits non signĂ©).AUDIO_S16MS - non pris en charge par tout le matĂ©riel (16 bits avec ordre d'octets Ă©levĂ©).AUDIO_U16SYS: AUDIO_U16LSB ou AUDIO_U16MSB - selon le processeur matĂ©riel.AUDIO_S16SYS: AUDIO_S16LSB ou AUDIO_S16MSB - selon le processeur matĂ©riel.SDL_Event
La structure de base pour les Ă©vĂ©nements.type : type d'Ă©vĂ©nement.active : Ă©vĂ©nement d'activation (voir SDL_ActiveEvent).key : Ă©vĂ©nement clavier (voir SDL_KeyboardEvent).motion : Ă©vĂ©nement de mouvement de la souris (voir SDL_MouseMotionEvent).bouton : Ă©vĂ©nement de clic de souris (voir SDL_MouseButtonEvent).jaxis : Ă©vĂ©nement de mouvement de l'axe du joystick (voir SDL_JoyAxisEvent).jball : Ă©vĂ©nement de mouvement du joystick trackball (voir SDL_JoyBallEvent).jhat : Ă©vĂ©nement de dĂ©placement de l'en-tĂȘte du joystick (voir SDL_JoyHatEvent).jbutton : Ă©vĂ©nement d'appuyer sur le bouton du joystick (voir SDL_JoyButtonEvent).redimensionner: ĂvĂ©nement de redimensionnement de la fenĂȘtre d'application (voir SDL_ResizeEvent).expose : Ă©vĂ©nement d'ouverture de fenĂȘtre d'application (voir SDL_ExposeEvent).quit : Ă©vĂ©nement de demande de sortie d'application (voir SDL_QuitEvent).user : Ă©vĂ©nement utilisateur (voir SDL_UserEvent).syswm : Ă©vĂ©nement de gestionnaire de fenĂȘtres non dĂ©fini (voir SDL_SysWMEvent).Voici les types d'Ă©vĂ©nements. Voir la documentation SDL pour plus d'informations:SDL_ACTIVEEVENT SDL_ActiveEventSDL_KEYDOWN / the UP SDL_KeyboardEventSDL_MOUSEMOTION SDL_MouseMotionEventSDL_MOUSEBUTTONDOWN / UPUPLLMouseButtonEventSDL_JOYAXISMOTION SDL_JoyAJOEL_JOSDL_JoyHatEvent SDL_JOYHATMOTIONSDL_JOYBUTTONDOWN / the UP SDL_JoyButtonEventSDL_VIDEORESIZESDL_ResizeEvent SDL_VIDEOEXPOSESDL_ExposeEvent SDL_QuitSDL_QuitEvent SDL_USEREVENTSDL_USEREVS SDL_USERESSDL_Overlay
Superposition YUV.format : format de superposition (voir ci-dessous).w, h : largeur / hauteur de la superposition.plans : le nombre de plans Ă superposer. Habituellement, soit 1 ou 3.emplacements : un tableau de retraits, un pour chaque plan. L'indentation est la longueur de la chaĂźne en octets.pixels : un tableau de pointeurs de donnĂ©es pour chaque plan. La superposition doit ĂȘtre verrouillĂ©e avant d'utiliser ces pointeurs.hw_overlay : dĂ©fini sur 1 si la superposition est accĂ©lĂ©rĂ©e par le matĂ©riel.SDL_Rect
La zone rectangulaire.Sint16 x, y : position du coin supérieur gauche du rectangle.Uint16 w, h : la largeur et la hauteur du rectangle.SDL_Rect définit une zone rectangulaire de pixels. Il est utilisé par SDL_BlitSurface pour identifier les zones de blitting et certaines autres fonctionnalités vidéo.SDL_Surface
Structure graphique du cĂŽtĂ© extĂ©rieur (surface).Uint32 drapeaux : Drapeaux des stotrons externes. Uniquement pour la lecture.Format SDL_PixelFormat * : lecture seule.int w, h : largeur et hauteur. Uniquement pour la lecture.Pas Uint16 : pas. Uniquement pour la lecture.void * pixels : un pointeur sur les donnĂ©es rĂ©elles des pixels. Pour l'enregistrement uniquement.SDL_Rect clip_rect : l'extĂ©rieur rectangulaire du clip. Uniquement pour la lecture.int refcount : utilisĂ© pour allouer de la mĂ©moire. Surtout pour la lecture.Cette structure contient Ă©galement des champs privĂ©s non reprĂ©sentĂ©s ici.SDL_Surface reprĂ©sente une zone de mĂ©moire "graphique" qui peut ĂȘtre dessinĂ©e. La trame de tampon vidĂ©o est renvoyĂ©e en tant que SDL_Surface Ă l'aide de SDL_SetVideoMode et SDL_GetVideoSurface. Les champs w et h sont des valeurs reprĂ©sentant la largeur et la hauteur de la surface en pixels. Le champ de pixels est un pointeur vers les donnĂ©es de pixels rĂ©elles. Remarque: la surface doit ĂȘtre verrouillĂ©e (via SDL_LockSurface) avant d'accĂ©der Ă ce champ. Le champ clip_rect est le rectangle dĂ©coupĂ© dĂ©fini par SDL_SetClipRect.Le champ indicateur prend en charge les valeurs OR suivantes:SDL_SWSURFACE - l'extĂ©rieur est stockĂ© dans la mĂ©moire systĂšme.SDL_HWSURFACE - Le cĂŽtĂ© externe est stockĂ© dans la mĂ©moire vidĂ©o.SDL_ASYNCBLIT - L'extĂ©rieur utilise l'Ă©blouissement asynchrone, si possible.SDL_ANYFORMAT - Tout format de pixel (surface d'affichage) est autorisĂ©.SDL_HWPALETTE - La surface a une palette exclusive.SDL_DOUBLEBUF - surface Ă double tampon (surface d'affichage).SDL_FULLSCREEN - surface plein Ă©cran (surface d'affichage).SDL_OPENGL - la surface a un contexte OpenGL (surface d'affichage).SDL_OPENGLBLIT - la surface prend en charge le blining OpenGL (surface d'affichage). REMARQUE. Cette option est uniquement pour la compatibilitĂ© et n'est pas recommandĂ©e pour le nouveau code.SDL_RESIZABLE - le redimensionnement est possible pour une surface (surface d'affichage).SDL_HWACCEL - le blit de surface utilise l'accĂ©lĂ©ration matĂ©rielle.SDL_SRCCOLORKEY - La superficialitĂ© utilise le mĂ©lange des couleurs.SDL_RLEACCEL - le dĂ©gradĂ© des couleurs est accĂ©lĂ©rĂ© Ă l'aide de RLE.SDL_SRCALPHA - Surface Blyth utilise le mĂ©lange alpha.SDL_PREALLOC - La surface utilise de la mĂ©moire prĂ©-allouĂ©e.SDL_Thread
Cette structure est indépendante du systÚme et vous n'avez probablement pas besoin de l'utiliser. Voir src / thread / sdl_thread_c.h dans le code source pour plus d'informations.SDL_cond
Cette structure est indépendante du systÚme et vous n'avez probablement pas besoin de l'utiliser. Voir src / thread / <system> /SDL_syscond.c dans le code source pour plus d'informations.SDL_mutex
Cette structure est indépendante du systÚme et vous n'avez probablement pas besoin de l'utiliser. Voir src / thread / <system> /SDL_sysmutex.c dans le code source pour plus d'informations.
Un didacticiel FFmpeg et SDL ou comment écrire un
lecteur vidéo en moins de 1 000 lignes FFmpeg , SDL FFmpeg HomePage SDL HomePage

A lire également sur le blog de la société EDISON:
FFmpeg libav manual