[الجزء 2/2] دليل FFmpeg و SDL أو كيفية كتابة مشغل فيديو في أقل من 1000 سطر


ننشر ما تبقى من ترجمة الدليل إلى اللغة الروسية ، والتي عفا عليها الزمن إلى حد ما ، لكنها لم تفقد أهميتها ، لأن هذا البرنامج التعليمي يساعد على فهم "المطبخ" لإنشاء تطبيقات الفيديو باستخدام مكتبات FFmpeg و SDL.

وعلى الرغم من محاولتنا ، إلا أن صعوبات الترجمة أمر لا مفر منه في مثل هذا النص الضخم . الإبلاغ عن الأخطاء (يُفضل أن يكون ذلك في الرسائل الخاصة) - سنعمل معًا بشكل أفضل.

جدول المحتويات


إديسون برمجيات - تطوير الويب
EDISON.

, .

, , Axxon Next SureView Immix.

! ;-)

6:


tutorial06.c
// tutorial05.c
// A pedagogical video player that really works!
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
// Use
//
// gcc -o tutorial05 tutorial05.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial04 myvideofile.mpg
//
// to play the video stream on your screen.

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <SDL.h>
#include <SDL_thread.h>

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include <stdio.h>
#include <assert.h>
#include <math.h>

// compatibility with newer API
#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; /* source height & width */
  int allocated;
  double pts;
} VideoPicture;

typedef struct VideoState {

  AVFormatContext *pFormatCtx;
  int             videoStream, audioStream;

  int             av_sync_type;
  double          external_clock; /* external clock base */
  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; /* used for AV difference average computation */
  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; ///<pts of last decoded frame / predicted pts of next decoded frame
  double          video_current_pts; ///<current displayed pts (different from video_clock if frame fifos are used)
  int64_t         video_current_pts_time;  ///<time (av_gettime) at which we updated video_current_pts - used to have running video pts
  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;

/* Since we only have one decoding thread, the Big Struct
   can be global in case we need it. */
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; /* maintained in the audio thread */
  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);
  }
}


/* Add or subtract samples to get a better sync, return new
   audio buffer size */
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 /*, nb_samples */;
    
    ref_clock = get_master_clock(is);
    diff = get_audio_clock(is) - ref_clock;

    if(diff < AV_NOSYNC_THRESHOLD) {
      // accumulate the diffs
      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) {
	    /* remove samples */
	    samples_size = wanted_size;
	  } else if(wanted_size > samples_size) {
	    uint8_t *samples_end, *q;
	    int nb;

	    /* add samples by copying final sample*/
	    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 {
      /* difference is TOO big; reset diff stuff */
      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) {
	/* if error, skip frame */
	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) {
	/* No data yet, get more frames */
	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);
      /* We have data, return it and come back for more later */
      return data_size;
    }
    if(pkt->data)
      av_free_packet(pkt);

    if(is->quit) {
      return -1;
    }
    /* next packet */
    if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
      return -1;
    }
    is->audio_pkt_data = pkt->data;
    is->audio_pkt_size = pkt->size;
    /* if update, update the audio clock w/pts */
    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) {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
      if(audio_size < 0) {
	/* If error, output silence */
	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; /* 0 means stop timer */
}

/* schedule a video refresh in 'delay' ms */
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; /* the pts from last time */
      if(delay <= 0 || delay >= 1.0) {
	/* if incorrect delay, use previous one */
	delay = is->frame_last_delay;
      }
      /* save for next time */
      is->frame_last_delay = delay;
      is->frame_last_pts = vp->pts;



      /* update delay to sync to audio if not master source */
      if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
	ref_clock = get_master_clock(is);
	diff = vp->pts - ref_clock;
	
	/* Skip or repeat the frame. Take delay into account
	   FFPlay still doesn't "know if this is the best guess." */
	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;
      /* computer the REAL delay */
      actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
      if(actual_delay < 0.010) {
	/* Really it should skip the picture instead */
	actual_delay = 0.010;
      }
      schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
      
      /* show the picture! */
      video_display(is);
      
      /* update queue for next picture! */
      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) {
    // we already have one make another, bigger/smaller
    SDL_FreeYUVOverlay(vp->bmp);
  }
  // Allocate a place to put our YUV image on that screen
  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;

  /* wait until we have space for a new pic */
  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;

  // windex is set to 0 initially
  vp = &is->pictq[is->pictq_windex];

  /* allocate or resize the buffer! */
  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;
    }
  }

  /* We have a place to put our picture on the queue */

  if(vp->bmp) {

    SDL_LockYUVOverlay(vp->bmp);
    vp->pts = pts;
    
    dst_pix_fmt = PIX_FMT_YUV420P;
    /* point pict at the queue */

    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];
    
    // Convert the image into YUV format that SDL uses
    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);
    /* now we inform our display thread that we have a pic ready */
    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) {
    /* if we have pts, set video clock to it */
    is->video_clock = pts;
  } else {
    /* if we aren't given a pts, set it to the clock */
    pts = is->video_clock;
  }
  /* update the video clock */
  frame_delay = av_q2d(is->video_ctx->time_base);
  /* if we are repeating a frame, adjust clock accordingly */
  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) {
      // means we quit getting packets
      break;
    }
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    pts = 0;

    // Decode video frame
    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);

    // Did we get a video frame?
    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; // Error copying codec context
  }


  if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
    // Set audio settings from codec info
    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;

  // Open video file
  if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
    return -1; // Couldn't open file

  is->pFormatCtx = pFormatCtx;
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, is->filename, 0);
  
  // Find the first video stream

  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;
  }

  // main decode loop

  for(;;) {
    if(is->quit) {
      break;
    }
    // seek stuff goes here
    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); /* no error; wait for user input */
	continue;
      } else {
	break;
      }
    }
    // Is this a packet from the video stream?
    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);
    }
  }
  /* all done - wait for it */
  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);
  }
  // Register all formats and codecs
  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);
  }

  // Make a screen to put our video
#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;
}


الآن بعد أن أصبح لدينا لاعب لائق أكثر أو أقل يمكنك حتى مشاهدة فيلم ، دعنا الآن نلبي احتياجاتنا. في المرة الأخيرة ، تطرقنا قليلاً إلى المزامنة ، أي مزامنة الصوت مع الفيديو ، بهذا الترتيب ، وليس العكس. سنجعل هذا نفس التخطيط كما هو الحال مع الفيديو: قم بعمل ساعة فيديو داخلية لتتبع مدى دفق الفيديو ومزامنة الصوت معه. سنقوم لاحقًا بتعميم المزيد - نقوم بمزامنة الصوت والفيديو مع ساعة خارجية.

تنفيذ ساعة الفيديو


نريد الآن إنشاء ساعة فيديو مشابهة للساعة الصوتية التي كانت لدينا في المرة الأخيرة: قيمة داخلية تُرجع الإزاحة الحالية للفيديو قيد التشغيل حاليًا. قد تعتقد أنه سيكون بسيطًا مثل تحديث المؤقت مع PTS الحالي للإطار الأخير المعروض. ومع ذلك ، لا تنس أن الوقت بين إطارات الفيديو قد يكون طويلًا جدًا إذا انخفضنا إلى مستوى المللي ثانية. لذلك ، فإن الحل هو تتبع قيمة أخرى ، وهي الوقت الذي يتم فيه تعيين ساعة الفيديو على PTS من الإطار الأخير. وبالتالي ، فإن القيمة الحالية لساعة الفيديو ستكون PTS_of_last_frame + ( current_time - time_elapsed_since_PTS_value_was_set) هذا الحل مشابه جدًا لما فعلناه مع get_audio_clock .

لذا ، سنضع ضعف video_current_pts و int64_t video_current_pts_time في هيكلنا الكامل . سيتم تحديث الساعة في وظيفة 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();

لا تنسَ تهيئته في stream_component_open :

is->video_current_pts_time = av_gettime();

والآن كل ما نحتاجه هو طريقة للحصول على المعلومات:

double get_video_clock(VideoState *is) {
  double delta;

  delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;
  return is->video_current_pts + delta;
}

تلخيص الساعة


ولكن لماذا تجبر نفسك على استخدام ساعة فيديو؟ يمكنك الذهاب إلى أبعد من ذلك وتغيير رمز مزامنة الفيديو الخاص بنا بحيث لا يحاول الصوت والفيديو مزامنة بعضهما البعض. تخيل ما ستكون الفوضى إذا حاولنا القيام بذلك باستخدام خيار سطر الأوامر ، كما هو الحال في FFplay. لذا ، دعنا نتخلص من ذلك : سننشئ وظيفة غلاف جديدة ، get_master_clock ، والتي تتحقق من المتغير av_sync_type ، ثم تستدعي get_audio_clock ، get_video_clock ، أو أي ساعة أخرى يمكن استخدامها. يمكننا حتى استخدام ساعة كمبيوتر ، والتي نسميها 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;
...
}

مزامنة الصوت


الآن الجزء الأصعب: مزامنة الصوت مع ساعة الفيديو. استراتيجيتنا هي قياس مكان الصوت ، ومقارنته بساعة الفيديو ، ثم معرفة عدد العينات التي نحتاج إلى ضبطها ، أي أننا بحاجة إلى الإسراع بإسقاط العينات أو إبطائها عن طريق الإضافة؟

نقوم بتشغيل وظيفة Syncize_audio في كل مرة نعالج فيها كل مجموعة من العينات الصوتية التي نحصل عليها من أجل تقليل هذه المجموعة أو زيادتها بشكل صحيح. ومع ذلك ، لا نريد المزامنة طوال الوقت ، لأن معالجة الصوت تحدث في كثير من الأحيان أكثر من معالجة حزم الفيديو. لذا ، سنقوم بتعيين الحد الأدنى لعدد المكالمات المتتالية لوظيفة Syncize_audioالتي تعتبر غير متزامنة قبل أن نتعب أنفسنا للقيام بأي شيء. بالطبع ، مثل آخر مرة ، يعني "عدم التزامن" أن الساعة الصوتية وساعة الفيديو تختلفان بمقدار أكبر من حد التزامن.

لذا سنستخدم معامل كسري ، على سبيل المثال ، s ، والآن ، لنفترض أننا حصلنا على Nمجموعات من العينات الصوتية غير متزامنة. يمكن أن يختلف أيضًا عدد العينات التي لا نقوم بمزامنتها بشكل كبير ، لذلك نأخذ متوسط ​​القيمة لمقدار عدم مزامنة كل منها. على سبيل المثال ، يمكن أن توضح المكالمة الأولى أننا غير متزامنين لمدة 40 مللي ثانية ، ثم التالية لمدة 50 مللي ثانية وما إلى ذلك. لكننا لن نأخذ وسيلة بسيطة ، لأن أحدث القيم أهم من تلك التي تسبقها. لذا ، سنستخدم معامل كسري ، على سبيل المثال ، c ، ولخص الاختلافات على النحو التالي: diff_sum = new_diff + diff_sum * c . عندما نكون مستعدين لإيجاد متوسط ​​الفرق ، فإننا ببساطة نحسب avg_diff =diff_sum * (1 - ج ).

ما يجري بحق الجحيم هنا؟ المعادلة تبدو وكأنها نوع من السحر. حسنًا ، هذا في الأساس متوسط ​​مرجح باستخدام سلسلة هندسية كأوزان. لا أعرف ما إذا كان هناك اسم لهذا (حتى راجعت على ويكيبيديا!) ، ولكن لمزيد من المعلومات ، إليك شرح (أو هنا: weightedmean.txt ).

إليك ما تبدو عليه وظيفتنا:

/* Add or subtract samples to get a better sync, return new
   audio buffer size */
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) {
      // accumulate the diffs
      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);

       /* Shrinking/expanding buffer code.... */

      }
    } else {
      /* difference is TOO big; reset diff stuff */
      is->audio_diff_avg_count = 0;
      is->audio_diff_cum = 0;
    }
  }
  return samples_size;
}

لذلك كل شيء على ما يرام معنا. نحن نعرف تقريبًا مقدار الصوت الذي لا يتوافق مع الفيديو أو مع ما نستخدمه كمشاهدة. لذا ، دعونا الآن نحسب عدد العينات التي نحتاج إلى إضافتها أو تجاهلها بوضع هذا الكود في قسم "تقليل / توسيع كود المخزن المؤقت":

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;
  }

تذكر أن length_length * ( sample_rate * # من القناة * 2) هي عدد العينات في ثوانٍ للطول الصوتي. لذلك ، سيكون عدد العينات التي نريدها مساويًا لعدد العينات التي لدينا بالفعل ، بالإضافة أو ناقص عدد العينات التي تتوافق مع مقدار الوقت الذي تم فيه تشغيل الصوت. سنحدد أيضًا حدًا لمدى حجم التصحيح أو صغره ، لأنه إذا قمنا بتغيير المخزن المؤقت لدينا كثيرًا ، فسيكون ذلك مزعجًا للغاية بالنسبة للمستخدم.

تصحيح عدد العينات


الآن نحن بحاجة إلى إصلاح الصوت. كنت قد لاحظت أن لدينا synchronize_audio وظيفة عائدات حجم العينة، الذي يروي لنا بعد ذلك عدد وحدات البايت لإرسالها إلى الدفق. لذلك نحتاج فقط إلى ضبط حجم العينة على القيمة المطلوبة. هذا يعمل على تقليل حجم العينة. ولكن إذا كنت بحاجة إلى زيادته ، فلا يمكننا زيادة حجم العينة فقط ، لأنه لا يوجد المزيد من البيانات في المخزن المؤقت! لذلك ، يجب أن نضيف القليل. ولكن ماذا تضيف بالضبط؟ سيكون من الحماقة محاولة استقراء الصوت ، لذلك دعونا نستخدم الصوت الموجود لدينا بالفعل ، مع إضافة قيمة العينة الأخيرة إلى المخزن المؤقت.

if(wanted_size < samples_size) {
  /* remove samples */
  samples_size = wanted_size;
} else if(wanted_size > samples_size) {
  uint8_t *samples_end, *q;
  int nb;

  /* add samples by copying final samples */
  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;
}

الآن نعيد حجم العينة ، وقد انتهينا بهذه الوظيفة. كل ما علينا فعله الآن هو استخدام هذا:

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) {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
      if(audio_size < 0) {
	/* If error, output silence */
	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;

كل ما فعلناه هو إدراج مكالمة Syncize_audio . (أيضًا ، تأكد من التحقق من شفرة المصدر ، التي نقوم فيها بتهيئة المتغيرات التي لم أزعج نفسك في تعريفها.)

والأخير ، قبل أن ننتهي: نحتاج إلى إضافة شرط "if" للتأكد من أننا لا نقوم بمزامنة الفيديو إذا كانت الساعة الرئيسية:

if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
  ref_clock = get_master_clock(is);
  diff = vp->pts - ref_clock;

  /* Skip or repeat the frame. Take delay into account
     FFPlay still doesn't "know if this is the best guess." */
  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;
    }
  }
}

ويعمل! تأكد من التحقق من الملف المصدر لتهيئة أي متغيرات لم أكلف نفسها عناء تعريفها أو تهيئتها. ثم اجمع:

gcc -o tutorial06 tutorial06.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`

وستكون الرحلة طبيعية.

في الدرس الأخير سنرجع.






الدرس 7: بحث


البرنامج التعليمي القائمة الكاملة 07.C
// tutorial05.c
// A pedagogical video player that really works!
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
// Use
//
// gcc -o tutorial05 tutorial05.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial04 myvideofile.mpg
//
// to play the video stream on your screen.

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <SDL.h>
#include <SDL_thread.h>

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include <stdio.h>
#include <assert.h>
#include <math.h>

// compatibility with newer API
#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; /* source height & width */
  int allocated;
  double pts;
} VideoPicture;

typedef struct VideoState {

  AVFormatContext *pFormatCtx;
  int             videoStream, audioStream;

  int             av_sync_type;
  double          external_clock; /* external clock base */
  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; /* used for AV difference average computation */
  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; ///<pts of last decoded frame / predicted pts of next decoded frame
  double          video_current_pts; ///<current displayed pts (different from video_clock if frame fifos are used)
  int64_t         video_current_pts_time;  ///<time (av_gettime) at which we updated video_current_pts - used to have running video pts
  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;

/* Since we only have one decoding thread, the Big Struct
   can be global in case we need it. */
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; /* maintained in the audio thread */
  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);
  }
}


/* Add or subtract samples to get a better sync, return new
   audio buffer size */
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 /*, nb_samples */;
    
    ref_clock = get_master_clock(is);
    diff = get_audio_clock(is) - ref_clock;

    if(diff < AV_NOSYNC_THRESHOLD) {
      // accumulate the diffs
      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) {
	    /* remove samples */
	    samples_size = wanted_size;
	  } else if(wanted_size > samples_size) {
	    uint8_t *samples_end, *q;
	    int nb;

	    /* add samples by copying final sample*/
	    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 {
      /* difference is TOO big; reset diff stuff */
      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) {
	/* if error, skip frame */
	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) {
	/* No data yet, get more frames */
	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);
      /* We have data, return it and come back for more later */
      return data_size;
    }
    if(pkt->data)
      av_free_packet(pkt);

    if(is->quit) {
      return -1;
    }
    /* next packet */
    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 update, update the audio clock w/pts */
    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) {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
      if(audio_size < 0) {
	/* If error, output silence */
	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; /* 0 means stop timer */
}

/* schedule a video refresh in 'delay' ms */
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; /* the pts from last time */
      if(delay <= 0 || delay >= 1.0) {
	/* if incorrect delay, use previous one */
	delay = is->frame_last_delay;
      }
      /* save for next time */
      is->frame_last_delay = delay;
      is->frame_last_pts = vp->pts;



      /* update delay to sync to audio if not master source */
      if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) {
	ref_clock = get_master_clock(is);
	diff = vp->pts - ref_clock;
	
	/* Skip or repeat the frame. Take delay into account
	   FFPlay still doesn't "know if this is the best guess." */
	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;
      /* computer the REAL delay */
      actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
      if(actual_delay < 0.010) {
	/* Really it should skip the picture instead */
	actual_delay = 0.010;
      }
      schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
      
      /* show the picture! */
      video_display(is);
      
      /* update queue for next picture! */
      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) {
    // we already have one make another, bigger/smaller
    SDL_FreeYUVOverlay(vp->bmp);
  }
  // Allocate a place to put our YUV image on that screen
  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;

  /* wait until we have space for a new pic */
  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;

  // windex is set to 0 initially
  vp = &is->pictq[is->pictq_windex];

  /* allocate or resize the buffer! */
  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;
    }
  }
  /* We have a place to put our picture on the queue */

  if(vp->bmp) {

    SDL_LockYUVOverlay(vp->bmp);
    vp->pts = pts;
    
    dst_pix_fmt = PIX_FMT_YUV420P;
    /* point pict at the queue */

    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];
    
    // Convert the image into YUV format that SDL uses
    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);
    /* now we inform our display thread that we have a pic ready */
    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) {
    /* if we have pts, set video clock to it */
    is->video_clock = pts;
  } else {
    /* if we aren't given a pts, set it to the clock */
    pts = is->video_clock;
  }
  /* update the video clock */
  frame_delay = av_q2d(is->video_ctx->time_base);
  /* if we are repeating a frame, adjust clock accordingly */
  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) {
      // means we quit getting packets
      break;
    }
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    pts = 0;

    // Decode video frame
    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);

    // Did we get a video frame?
    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; // Error copying codec context
  }


  if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
    // Set audio settings from codec info
    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;

  // Open video file
  if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
    return -1; // Couldn't open file

  is->pFormatCtx = pFormatCtx;
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, is->filename, 0);
  
  // Find the first video stream

  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;
  }

  // main decode loop

  for(;;) {
    if(is->quit) {
      break;
    }
    // seek stuff goes here
    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); /* no error; wait for user input */
	continue;
      } else {
	break;
      }
    }
    // Is this a packet from the video stream?
    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);
    }
  }
  /* all done - wait for it */
  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);
  }
  // Register all formats and codecs
  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);
  }

  // Make a screen to put our video
#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;
      /*
       * If the video has finished playing, then both the picture and
       * audio queues are waiting for more data.  Make them stop
       * waiting and terminate normally.
       */
      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;

}

معالجة أوامر البحث


سنقوم الآن بإضافة بعض إمكانيات البحث في مشغلنا ، لأنه أمر مزعج حقًا عندما لا يمكنك إرجاع الفيلم. بالإضافة إلى ذلك ، سنرى مدى سهولة استخدام وظيفة av_seek_frame .

سنجعل الأسهم على لوحة المفاتيح "يسار" و "يمين" يمرر الفيلم للأمام والخلف قليلاً ، والسهام "لأعلى" و "لأسفل" أكثر أهمية بالفعل. "القليل" - ستكون 10 ثوانٍ ، و "الكثير" - كل 60. لذلك ، نحتاج إلى تكوين الحلقة الرئيسية لدينا بحيث تعترض أحداث الضغط على المفاتيح. لكن الحقيقة هي أنه عندما نحصل على ضغطة مفتاح ، لا يمكننا الاتصال بـ av_seek_frame مباشرة. هذا يجب أن يتم في هدفنا الرئيسي فك حلقة، decode_thread حلقة. لذلك ، بدلاً من ذلك ، سنضيف بعض القيم إلى الهيكل الرئيسي ، والذي سيحتوي على موضع جديد للبحث وبعض علامات البحث:

  int             seek_req;
  int             seek_flags;
  int64_t         seek_pos;

الآن نحن بحاجة إلى تكوين الحلقة الرئيسية التي تلتقط ضغطات المفاتيح:

  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;

للقبض على ضغطة مفتاح ، ننظر أولاً إلى ما إذا كان حدث SDL_KEYDOWN حدث . ثم نتحقق من المفتاح الذي تم استلامه باستخدام event.key.keysym.sym . بمجرد معرفة الاتجاه الذي نتطلع إليه ، نحسب وقتًا جديدًا ، ونضيف زيادة في القيمة من دالة get_master_clock الجديدة . ثم نسميه stream_seek وظيفة لتعيين seek_pos القيم ، الخ قم بتحويل وقتنا الجديد إلى وحدات الطابع الزمني الداخلية avcodec . تذكر أن الطوابع الزمنية في التدفقات تقاس بإطارات ، وليس بالثواني ، باستخدام الصيغة التالية: seconds = frames * time_base ( fps ).بشكل افتراضي ، يتم تعيين avcodec على 1،000،000 إطار في الثانية (وبالتالي فإن موضع 2 ثانية سيكون له طابع زمني قدره 2،000،000). لماذا نحتاج إلى تحويل هذه القيمة - انظر لاحقًا.

هذه هي وظيفة stream_seek . لاحظ أننا وضعنا العلم إذا عدنا:

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;
  }
}

الآن دعنا ننتقل إلى decode_thread ، حيث نجري بحثًا بالفعل. في ملفات المصدر يمكنك أن ترى أننا وضعنا علامة على المنطقة "البحث قيد التقدم". حسنًا ، سنضعها هناك الآن.

يتمحور البحث حول دالة av_seek_frame . تأخذ هذه الوظيفة كحجة سياق التنسيق ، والدفق ، والطابع الزمني ، ومجموعة العلم. ستبحث الوظيفة عن الطابع الزمني الذي تعطيه له. وحدة الطابع الزمني هي قاعدة الوقت للدفق التي تمررها إلى الوظيفة. ومع ذلك ، لا تحتاج إلى تمريره إلى الدفق (يشار إليه بتمرير القيمة -1). إذا قمت بذلك ، سيكون time_base في وحدة الوقت الداخلية avcodecأو 1000000 إطارًا في الثانية. هذا هو السبب في أننا ضربنا موقفنا في AV_TIME_BASE عندما قمنا بتعيين السعي .

ومع ذلك ، في بعض الأحيان يمكنك (نادرًا) مواجهة مشكلات لبعض الملفات إذا قمت بتمرير av_seek_frame - 1 للدفق ، لذلك سنقوم بتحديد الدفق الأول في ملفنا وتمريره إلى av_seek_frame . لا تنس أنه يجب علينا تغيير مقياس طابعنا الزمني حتى نكون في "نظام الإحداثيات" الجديد.

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 {
     /* handle packet queues... more later... */

av_rescale_q ( أ ، ب ، ج ) هي وظيفة تقوم بتغيير حجم الطابع الزمني من قاعدة إلى أخرى. وتحسب أساسا ل * ب / ج ، ولكن يأتي هذا في متناول اليدين وظيفة لهذا الحساب في بعض الأحيان يؤدي إلى تجاوز. AV_TIME_BASE_Q هو نسخة كسرية من AV_TIME_BASE . إنها مختلفة تمامًا: AV_TIME_BASE * time_in_seconds = avcodec_timestamp و AV_TIME_BASE_Q * avcodec_timestamp = time_in_seconds (لكن لاحظ أن AV_TIME_BASE_Qهو في الواقع كائن AVRational ، لذلك تحتاج إلى استخدام وظائف q الخاصة في avcodec لمعالجتها ).

تنظيف عازلة


لذا ، قمنا بإعداد عمليات البحث بشكل صحيح ، ولكننا لم ننتهي بعد. تذكر ، هل لدينا قائمة انتظار تم تكوينها لتجميع الحزم؟ الآن بعد أن أصبحنا في طابع زمني مختلف ، نحتاج إلى مسح قائمة الانتظار هذه ، وإلا فلن يعمل البحث في الفيلم! بالإضافة إلى ذلك ، لدى avcodec مخازن داخلية خاصة بها ، والتي تحتاج أيضًا إلى مسحها لكل تيار.

للقيام بذلك ، يجب عليك أولاً كتابة دالة تعمل على مسح قائمة انتظار الحزم الخاصة بنا. ثم تحتاج إلى إرشاد دفق الصوت والفيديو بطريقة أو بأخرى التي مسحها المخازن المؤقتة الداخلية avcodec . يمكننا القيام بذلك عن طريق وضع حزمة خاصة في قائمة الانتظار بعد تنظيفها ، وعندما يكتشفون (الخيوط) هذه الحزمة الخاصة ، سيقومون ببساطة بمسح مخازنهم المؤقتة.

لنبدأ بوظيفة إعادة الضبط. إنه حقًا بسيط جدًا ، لذا سأعرض لك الرمز:

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);
}

الآن بعد أن تم مسح قائمة الانتظار ، أضف "حزمة التنظيف" الخاصة بنا. ولكن أولاً ، سيكون من الجيد تحديد ماهيته وإنشائه:

AVPacket flush_pkt;

main() {
  ...
  av_init_packet(&flush_pkt);
  flush_pkt.data = "FLUSH";
  ...
}

الآن ضع هذه الحزمة في قائمة الانتظار:

  } 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;
}

(يواصل مقتطف الشفرة هذا مقتطف الشفرة أعلاه لـ decode_thread .) نحتاج أيضًا إلى تعديل packet_queue_put بحيث لا نكرر حزمة خاصة للتنظيف:

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {

  AVPacketList *pkt1;
  if(pkt != &flush_pkt && av_dup_packet(pkt) < 0) {
    return -1;
  }

ثم في دفق الصوت والفيديو نضع هذه المكالمة في avcodec_flush_buffers مباشرة بعد 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;
    }

مقتطف الشفرة أعلاه هو نفسه تمامًا لدفق الفيديو ، مع استبدال "صوت" بـ "فيديو".

هذه هي! لقد فعلناها! اجمع لاعبك:

gcc -o tutorial07 tutorial07.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`

واستمتع بمشغل الأفلام الخاص بك المصنوع في أقل من 1000 سطر من C!

على الرغم ، بالطبع ، هناك الكثير من الأشياء التي يمكن إضافتها أو تحسينها.






خاتمة


لذا ، لدينا لاعب عامل ، ولكن بالطبع ليست جيدة كما يمكن أن تكون. سيكون من الممكن تعديل الملف وإضافة الكثير من الأشياء المفيدة:

  • دعونا نواجه الأمر ، هذا اللاعب سيء. إصدار ffplay.c الذي يستند إليه قديم تمامًا ، ونتيجة لذلك ، يحتاج هذا البرنامج التعليمي إلى المراجعة الشاملة. إذا كنت تريد الانتقال إلى مشروعات أكثر جدية باستخدام مكتبات FFmpeg ، فإنني أوصي بشدة بالتحقق من أحدث إصدار من ffplay.c كمهمة تالية.
  • إن معالجة الخطأ في الكود الخاص بنا أمر فظيع ويمكن تنفيذه بشكل أفضل.
  • , , , . , paused , , . , , . av_read_play. - , . , , . : , ffplay.c.
  • .
  • . , , , , VOB-.
  • . , .
  • . .
  • , , , , YUV, time_base.
  • .
  • --, ; ffplay.c .


إذا كنت ترغب في معرفة المزيد عن FFmpeg ، فقد نظرنا هنا بعيدًا عن كل شيء. الخطوة التالية هي دراسة ترميز الوسائط المتعددة. من الأفضل أن تبدأ بالملف output_example.c ، والذي ستجده في توزيع FFmpeg. يمكنني كتابة كتاب مدرسي آخر حول هذا الموضوع بالفعل ، ولكن من غير المحتمل أن يتجاوز هذا الدليل.

UPD.منذ وقت طويل لم أقم بتحديث هذا النص ، ولكن في الوقت نفسه لا يقف العالم ثابتًا. يتطلب هذا البرنامج التعليمي تحديثات API بسيطة فقط ؛ لقد تغير القليل جدا من حيث المفاهيم الأساسية. لقد قامت معظم هذه التحديثات بتبسيط الكود بالفعل. ومع ذلك ، على الرغم من أنني مشيت في جميع أنحاء الكود وقمت بتحديثه ، لا يزال FFplay متفوقًا على مشغل الألعاب هذا. نعترف قلبًا: في هذه الدروس كتبنا لاعب فيلم رديء جدًا. لذلك ، إذا كنت تريد اليوم (أو في المستقبل) تحسين هذا البرنامج التعليمي ، أوصي بأن تتعرف على FFplay وتكتشف ما هو مفقود. أعتقد أن هذا يتعلق أساسًا باستخدام معدات الفيديو ، ولكن ربما أفتقد بعض الأشياء الواضحة الأخرى. ربما تؤدي المقارنة مع FFplay الحالي إلى إعادة كتابة جذرية لبعض الأشياء - لم أشاهدها بعد.

لكنني فخور جدًا بأن عملي قد ساعد كثيرًا على مر السنين ، حتى مع مراعاة حقيقة أن الأشخاص غالبًا ما كانوا يبحثون عن رمز في مكان آخر. أنا ممتن للغاية لـ Chelyaev ، الذي أخذ على عاتقه روتين استبدال جميع الوظائف التي عفا عليها الزمن منذ أن كتبت هذه الدراسة منذ 8 سنوات (!).

أفرح على أمل أن تكون هذه الدروس مفيدة وليست مملة. إذا كان هناك أي اقتراحات أو أخطاء أو شكاوى أو شكر ، وما إلى ذلك فيما يتعلق بهذا الدليل ، يرجى الكتابة إلي على dranger dog gmail dot com. ونعم ، ليس من المنطقي أن تطلب مني المساعدة في مشروع FFmpeg الخاص بك. هناك أيضا العديد من رسائل مماثلة .






الملحق 1. قائمة الوظائف


int avformat_open_input(AVFormatContext **ptr, const char * filename, AVInputFormat *fmt, AVDictionary **options)

يفتح اسم ملف الوسائط ويحفظ سياق التنسيق في العنوان المحدد في ptr .

fmt : إذا لم يكن NULL ، فسيقوم بتعيين تنسيق الملف.
buf_size : حجم المخزن المؤقت (اختياري).
الخيارات : يتم ملء AVDictionary بمعلمات AVFormatContext و demultiplexer.

void avformat_close_input(AVFormatContext **s)

لإغلاق ملف الوسائط. ومع ذلك ، فإنه لا يغلق برامج الترميز.

nt avio_open2 (AVIOContext **s, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options)

إنشاء سياق إدخال / إخراج لاستخدام المورد المحدد في عنوان url .

s : المؤشر إلى المكان الذي سيتم فيه إنشاء نص AVIOContext . في حالة الفشل ، يتم تعيين القيمة المحددة إلى NULL.
url : اسم المورد للوصول إليه.
الإشارات : التحكم في فتح المورد المحدد في عنوان url .
int_cb : رد اتصال المقاطعة لاستخدام مستوى البروتوكول.
الخيارات : قاموس معبأ بمعلمات البروتوكول الخاص. عندما تعود الدالة ، سيتم تدمير المعلمة واستبدالها بإملاء يحتوي على خيارات لم يتم العثور عليها. قد تكون فارغة.

int av_dup_packet(AVPacket *pkt)

بالطبع ، هذا اختراق: إذا لم يتم تخصيص هذه الحزمة ، فإننا ننشرها هنا. إرجاع 0 عند النجاح أو AVERROR_NOMEM عند الفشل.

int av_find_stream_info(AVFormatContext *s, AVDictionary **options)

تبحث هذه الوظيفة عن معلومات دفق غير واضحة ، مثل معدل الإطارات. يفيد ذلك في تنسيقات الملفات الخالية من العناوين مثل MPEG. يوصى بالاتصال بعد فتح الملف. إرجاع> = 0 في حالة النجاح ، AVERROR_ * في حالة حدوث خطأ.

AVFrame *avcodec_free_frame()

الاسم القديم لـ av_frame_free. تم التغيير في LAVC 55.28.1.

void av_frame_free (AVFrame **frame)

يطلق إطارًا وأي كائنات مخصصة ديناميكيًا فيه ، على سبيل المثال ، بيانات موسعة.

void av_free(void *ptr)

يطلق الذاكرة المخصصة باستخدام av_malloc () أو av_realloc (). يمكنك استدعاء هذه الوظيفة مع ptr == NULL. من المستحسن استدعاء av_freep () بدلاً من ذلك.

void av_freep(void *ptr)

يحرر الذاكرة ويضبط المؤشر على NULL. يستخدم داخليًا av_free ().

void av_free_packet(AVPacket *pkt)

التفاف حول طريقة تدمير الحزمة (pkt-> التدمير).

int64_t av_gettime()

احصل على الوقت الحالي بالميكروثانية.

void av_init_packet(AVPacket *pkt)

تهيئة حقول الحزمة الاختيارية.

void *av_malloc(unsigned int size)

حجم بايت تخصيص الذاكرة مع محاذاة مناسبة لجميع عمليات الوصول إلى الذاكرة (بما في ذلك المتجهات ، إذا كانت متوفرة على وحدة المعالجة المركزية). يجب أن يعيد av_malloc (0) مؤشر غير صفري.

void *av_mallocz(unsigned int size)

يشبه av_malloc () ، ولكن تهيئة الذاكرة إلى الصفر.

double av_q2d(AVRational a)

يضاعف AVRational.

int av_read_frame(AVFormatContext *s, AVPacket *pkt)

إرجاع إطار الدفق التالي. يتم تخزين المعلومات كحزمة في pkt.

الحزمة التي تم إرجاعها صالحة حتى av_read_frame () أو حتى av_close_input_file () ويجب تحريرها باستخدام av_free_packet. لحزمة فيديو تحتوي على إطار واحد بالضبط. بالنسبة إلى الصوت ، يحتوي على عدد صحيح من الإطارات إذا كان لكل إطار حجم ثابت معروف (على سبيل المثال ، بيانات PCM أو ADPCM). إذا كانت إطارات الصوت ذات حجم متغير (على سبيل المثال ، صوت MPEG) ، فإنها تحتوي على إطار واحد.

يتم تعيين مدة pkt-> pts و pkt-> dts و pkt-> دائمًا على القيم الصحيحة في وحدات AVStream.timebase (ويفترض أن التنسيق لا يمكنه توفيرها). pkt-> pts يمكن أن تكون AV_NOPTS_VALUE إذا كان تنسيق الفيديو به إطارات B ، لذا من الأفضل الاعتماد على pkt-> dts إذا لم تكن تفريغ الحمولة.

النتيجة التي تم إرجاعها: 0 ، إذا كان كل شيء على ما يرام ، <0 ، إذا كان هناك خطأ أو نهاية الملف.

void av_register_all();

يسجل جميع برامج الترميز في المكتبة.

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

إرجاع a * bq / cq .

int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)

يبحث عن إطار رئيسي في طابع زمني.

stream_index : إذا كان stream_index يساوي -1 ، فسيتم تحديد الدفق الافتراضي ويتم تحويل الطابع الزمني تلقائيًا من وحدات AV_TIME_BASE إلى قاعدة زمنية خاصة بالدفق.
الطابع الزمني : الطابع الزمني المقاس بوحدات AVStream.time_base أو ، إذا لم يتم تحديد دفق ، في وحدات AV_TIME_BASE.
الإشارات : ضبط المعلمات المتعلقة بالاتجاه ووضع البحث:
AVSEEK_FLAG_ANY: البحث في أي إطار ، وليس فقط في الإطارات الرئيسية.
AVSEEK_FLAG_BACKWARD: البحث في الاتجاه المعاكس.
AVSEEK_FLAG_BYTE: البحث بناءً على الموضع بالبايت.

AVFrame *avcodec_alloc_frame()

الاسم القديم لـ av_frame_alloc. تم التغيير في LAVC 55.28.1.

AVFrame *av_frame_alloc()

تحديد AVFrame وتهيئته. يمكن تحريره باستخدام av_frame_free ().

int avcodec_decode_audio4(AVCodecContext *avctx, AVFrame *frame, int *got_frame_ptr, const AVPacket *avpkt)

فك تشفير إطار صوتي من avpkt إلى الإطار. تقوم الدالة avcodec_decode_audio4 () بفك تشفير ملف صوتي من AVPacket. لفك ترميزه ، يتم استخدام برنامج ترميز صوتي مرتبط بـ avctx باستخدام avcodec_open2 (). يتم تخزين الإطار الذي تم فك تشفيره في AVFrame المحدد. إذا تم تفكيك الإطار ، فسيقوم بضبط got_frame_ptr على 1.

تحذير: يجب أن يكون المخزن المؤقت للإدخال ، avpkt-> البيانات أكبر من FF_INPUT_BUFFER_PADDING_SIZE بقراءة وحدات البايت الفعلية ، لأن بعض أجهزة قراءة البت الأساسية المحسنة تقرأ 32 أو 64 بت في المرة الواحدة ويمكنها القراءة لما يصل إلى النهاية.

avctx : سياق الكوديك.
الإطار : الإطار الهدف.
got_frame_ptr : الهدف int ، الذي سيتم تعيينه إذا تم فك الإطار.
AVPKT: AVPacket يحتوي على صوت.

النتيجة التي تم إرجاعها: إذا تم إرجاع خطأ ، يتم إرجاع قيمة سالبة ؛ وإلا ، يتم إرجاع عدد وحدات البايت المستخدمة من إدخال AVPacket.

int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *frameFinished, const AVPacket *avpkt)

يفك تشفير إطار الفيديو من buf إلى صورة. تقوم الدالة avcodec_decode_video2 () بفك تشفير إطار فيديو من مخزن مؤقت للإدخال بحجم buf_size. لفك ترميزه ، يتم استخدام برنامج ترميز الفيديو ، والذي كان مرتبطًا بـ avctx باستخدام avcodec_open2 (). يتم حفظ الإطار الذي تم فك تشفيره في الصورة.

تحذير: أمثلة المحاذاة ومشاكل التخزين المؤقت التي تنطبق على avcodec_decode_audio4 تنطبق على هذه الوظيفة أيضًا.

avctx : سياق الكوديك.
الصورة : AVFrame الذي سيتم فيه حفظ الفيديو الذي تم فك تشفيره.
frameFinished : صفر إذا لم يكن بالإمكان فك الإطارات ، وإلا فلن تساوي صفرًا.
avpkt: إدخال AVPacket يحتوي على مخزن الإدخال المؤقت. يمكنك إنشاء مثل هذه الحزمة باستخدام av_init_packet () ، وبعد تحديد البيانات والحجم المحدد ، قد تحتاج بعض أجهزة فك التشفير أيضًا إلى حقول أخرى ، مثل الإشارات و AV_PKT_FLAG_KEY. تم تصميم جميع أجهزة فك التشفير لاستخدام أقل عدد ممكن من الحقول.

النتيجة التي تم إرجاعها: عند الخطأ ، يتم إرجاع قيمة سالبة ، وإلا يتم استخدام عدد وحدات البايت أو صفر إذا لم يكن بالإمكان فك ضغط الإطارات.

int64_t av_frame_get_best_effort_timestamp (const AVFrame *frame)

طريقة وصول بسيطة للحصول على best_effort_timestamp من كائن AVFrame.

AVCodec *avcodec_find_decoder(enum CodecID id)

يبحث عن وحدة فك ترميز باستخدام CodecID. إرجاع NULL عند الخطأ. يجب أن يتم استدعاؤها بعد الحصول على AVCodecContext المطلوب من الدفق في AVFormatContext باستخدام codecCtx-> codec_id.

void avcodec_flush_buffers(AVCodecContetx *avctx)

تدفق العازلة. يتم الاتصال به عند البحث أو التبديل إلى دفق آخر.

AVCodecContext * avcodec_alloc_context3 (const AVCodec *codec)

يعين AVCodecContext ويعين حقوله على القيم الافتراضية.

int avcodec_copy_context (AVCodecContext *dest, const AVCodecContext *src)

انسخ إعدادات AVCodecContext المصدر إلى AVCodecContext الهدف. سيتم إغلاق السياق الناتج من برنامج ترميز الوجهة ، أي يجب عليك الاتصال بـ avcodec_open2 () قبل استخدام AVCodecContext هذا لفك ترميز / ترميز بيانات الفيديو / الصوت.

dest : يجب أن تتم تهيئته باستخدام avcodec_alloc_context3 (NULL) ، وإلا فلن تتم تهيئته.

int avcodec_open2(AVCodecContext *avctx, AVCodec *codec, AVDictionary **options)

تهيئة avctx لاستخدام برنامج الترميز المحدد في برنامج الترميز . يجب استخدامه بعد avcodec_find_decoder. إرجاع صفر عند النجاح وقيمة سالبة عند الخطأ.

int avpicture_fill(AVPicture *picture, uint8_t *ptr, int pix_fmt, int width, int height)

يضبط البنية التي تشير إليها الصورة ، مع المخزن المؤقت ptr ، تنسيق pix_fmt ، والعرض والارتفاع المحددين. إرجاع حجم بيانات الصورة بالبايت.

int avpicture_get_size(int pix_fmt, int width, int height)

لحساب عدد وحدات البايت المطلوبة لصورة ذات عرض ، ارتفاع وتنسيق صورة.

struct SwsContext* sws_getContext(int srcW, int srcH, int srcFormat, int dstW, int dstH, int dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, double *param)

إرجاع SwsContext للاستخدام في sws_scale.

srcW و srcH و srcFormat : عرض وارتفاع وتنسيق وحدات البكسل المطلوبة.
dstW و dstH و dstFormat : عرض وارتفاع وتنسيق وحدات البكسل النهائية.
أعلام : طريقة التحجيم للاستخدام.
الخيارات التالية متاحة: SWS_FAST_BILINEAR و SWS_BILINEAR و SWS_BICUBIC و SWS_X و SWS_POINT و SWS_AREA و SWS_BICUBLIN و SWS_GAUSS و SWS_SINC و SWS_LANCZOS و SWS_SPLINE.
تتضمن العلامات الأخرى إشارات قدرة وحدة المعالجة المركزية: SWS_CPU_CAPS_MMX و SWS_CPU_CAPS_MMX2 و SWS_CPU_CAPS_3DNOW و SWS_CPU_CAPS_ALTIVEC.
تشمل العلامات الأخرى (التي لم يتم تنفيذها بالكامل حاليًا) SWS_FULL_CHR_H_INT و SWS_FULL_CHR_H_INP و SWS_DIRECT_BGR.
أخيرًا ، هناك SWS_ACCURATE_RND وربما الأكثر فائدة للمبتدئين ، SWS_PRINT_INFO.
ليس لدي أي فكرة عما يفعله معظمهم. ربما اكتب لي؟
srcFilter ، dstFilter : SwsFilter للمصدر والوجهة. يتيح SwsFilter فلترة الألوان / السطوع. القيمه الافتراضيه فارغه.
param : يجب أن يكون مؤشرًا لمخزن مؤقت int [2] مع المعاملات. غير موثق. يبدو أنه يستخدم لتعديل خوارزميات القياس القياسية قليلاً. القيمه الافتراضيه فارغه. فقط للخبراء!

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);

يقوم بقياس البيانات بتنسيق src وفقًا لإعداداتنا في SwsContext * c .
srcStride و dstStride هما أحجام صف المصدر والوجهة.

SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param)

يضيف وظيفة رد اتصال يتم تشغيلها بعد عدد محدد من المللي ثانية. تقوم وظيفة رد الاتصال بتمرير الفاصل الزمني للموقت الحالي والمعلمة التي يوفرها المستخدم من استدعاء SDL_AddTimer وإرجاع الفاصل الزمني للموقت التالي. (إذا تطابق القيمة المرجعة لرد الاتصال مع القيمة التي تم تمريرها ، يستمر المؤقت في العمل بنفس السرعة.) إذا كانت قيمة الإرجاع لرد الاتصال هي 0 ، فسيتم إلغاء المؤقت.
هناك طريقة أخرى لإلغاء المؤقت الحالي وهي استدعاء SDL_RemoveTimer باستخدام معرف المؤقت (الذي تم إرجاعه من SDL_AddTimer).

قد يتم تنفيذ وظيفة رد الاتصال للمؤقت على مؤشر ترابط مختلف عن برنامجك الرئيسي ، وبالتالي لا يجب استدعاء أي وظائف من تلقاء نفسها. ومع ذلك ، يمكنك دائمًا الاتصال بـ SDL_PushEvent.

تعتمد درجة تفاصيل المؤقت على النظام الأساسي ، ولكن يجب أن تتوقع أنه لا يقل عن 10 مللي ثانية ، لأن هذه هي القيمة الأكثر شيوعًا. هذا يعني أنه إذا طلبت مؤقت 16 مللي ثانية ، فسيبدأ رد الاتصال بعد حوالي 20 مللي ثانية على نظام غير محمل. إذا كنت بحاجة إلى تعيين إشارة تشير إلى تحديث الإطارات بسرعة 30 إطارًا في الثانية (كل 33 مللي ثانية) ، يمكنك تعيين مؤقت لمدة 30 مللي ثانية (انظر المثال أدناه). إذا كنت تستخدم هذه الوظيفة ، فأنت بحاجة إلى تمرير SDL_INIT_TIMER إلى SDL_Init.

إرجاع قيمة المعرف لجهاز ضبط الوقت المضاف أو NULL في حالة حدوث خطأ.

تنسيق رد الاتصال:
Uint32 callback ( Uint32, void * param)


int SDL_CondSignal(SDL_cond *cond)

إعادة تشغيل أحد مؤشرات الترابط في انتظار متغير حالة Cond . إرجاع 0 عند النجاح و -1 عند الخطأ.

int SDL_CondWait(SDL_cond *cond, SDL_mutex *mut);

قم بإلغاء تأمين كائن المزامنة المقدم وانتظر سلسلة رسائل أخرى لاستدعاء SDL_CondSignal أو SDL_CondBroadcast لمتغير الشرط ، ثم أعد قفل ملف المزامنة. يجب تأمين كائن المزامنة قبل دخول هذه الوظيفة. إرجاع 0 عند تلقي إشارة أو -1 عند الخطأ.

SDL_cond *SDL_CreateCond(void);

ينشئ متغير شرط.

SDL_Thread *SDL_CreateThread(int (*fn)(void *), void *data);

يُنشئ SDL_CreateThread سلسلة تنفيذ جديدة تشارك في الذاكرة العالمية بأكملها لأصلها ، ومعالجات الإشارة ، وواصفات الملف ، إلخ. ويقوم بتشغيل دالة fn ، ويمررها بيانات مؤشر الفراغ. مؤشر الترابط ينتهي عندما تقوم fn بإرجاع قيمة.

void SDL_Delay (Uint32 );

ينتظر العدد المحدد من المللي ثانية. SDL_Delay سينتظر على الأقل الوقت المحدد ، ولكن ربما أطول بسبب تخطيط نظام التشغيل.
ملحوظة: توقع دقة تأخير لا تقل عن 10 مللي ثانية. تحتوي بعض الأنظمة الأساسية على مقاييس أقصر ، ولكن هذا هو الخيار الأكثر شيوعًا.

SDL_Overlay *SDL_CreateYUVOverlay(int width, int height, Uint32 format, SDL_Surface *display);

SDL_CreateYUVOverlay ينشئ تراكب YUV للعرض والارتفاع والتنسيق المحدد (للحصول على قائمة بالتنسيقات المتاحة ، انظر بنية بيانات SDL_Overlay) للعرض المقدم. إرجاع SDL_Overlay. يجب أن تكون

الشاشة في الواقع سطحًا مشتقًا من SDL_SetVideoMode ، وإلا ستعمل هذه الوظيفة افتراضيًا.

مصطلح "تراكب" غير صحيح ، لأنه إذا لم يتم إنشاء التراكب في الأجهزة ، فسيتم استبدال محتويات سطح العرض أسفل المنطقة التي يتم فيها عرض التراكب عند عرض التراكب.

int SDL_LockYUVOverlay(SDL_Overlay *overlay)

يعمل SDL_LockYUVO على حظر التراكب للوصول المباشر إلى بيانات البكسل. إرجاع 0 عند النجاح أو -1 عند الخطأ.

void SDL_UnlockYUVOverlay(SDL_Overlay *overlay)

يفتح تراكب مقفل سابقا. يجب إلغاء تأمين التراكب قبل أن يتم عرضه.

int SDL_DisplayYUVOverlay(SDL_Overlay *overlay, SDL_Rect *dstrect)

يضع التراكب على السطح المحدد عند إنشائه. تحدد بنية dstrect SDL_Rect موضع وحجم الوجهة. إذا كان dstrect هو تراكب أكثر أو أقل ، فسيتم تحجيم التراكب ، حيث يتم تحسين هذا للتكبير 2x. إرجاع 0 إذا نجح.

void SDL_FreeYUVOverlay(SDL_Overlay *overlay)

يطلق التراكب الذي تم إنشاؤه بواسطة SDL_CreateYUVOverlay.

int SDL_Init(Uint32 flags);

تهيئة SDL. يجب استدعاء هذا قبل كافة وظائف SDL الأخرى. تحدد معلمة الإشارات أجزاء SDL المراد تهيئتها.

SDL_INIT_TIMER - تهيئة النظام الفرعي للمؤقت.
SDL_INIT_AUDIO - تهيئة النظام الفرعي الصوتي.
SDL_INIT_VIDEO - تهيئة النظام الفرعي للفيديو.
SDL_INIT_CDROM - تهيئة النظام الفرعي للقرص المضغوط.
SDL_INIT_JOYSTICK - تهيئة النظام الفرعي لعصا التحكم.
SDL_INIT_EVERYTHING - تهيئة كل ما سبق.
SDL_INIT_NOPARACHUTE - لا يسمح لـ SDL بالتقاط الأخطاء الفادحة.
SDL_INIT_EVENTTHREAD - يقوم بتشغيل مدير الأحداث في سلسلة محادثات منفصلة.

إرجاع -1 على خطأ أو 0 عند النجاح. يمكنك الحصول على رسالة خطأ موسعة عن طريق استدعاء SDL_GetError. السبب المعتاد للخطأ هو استخدام شاشة عرض محددة دون دعم مقابل للنظام الفرعي ، على سبيل المثال ، عدم وجود برنامج تشغيل الماوس عند استخدام مخزن مؤقت للإطار مع الجهاز. في هذه الحالة ، يمكنك إما تجميع SDL بدون ماوس ، أو تعيين متغير البيئة "SDL_NOMOUSE = 1" قبل بدء التطبيق.

SDL_mutex *SDL_CreateMutex(void);

إنشاء كائن مزامنة جديد غير مؤمن.

int SDL_LockMutex(SDL_mutex *mutex)

SDL_LockMutex هو اسم مستعار لـ SDL_mutexP. يحظر كائن المزامنة الذي تم إنشاؤه مسبقًا باستخدام SDL_CreateMutex. إذا تم حظر كائن المزامنة بالفعل بواسطة مؤشر ترابط آخر ، فلن يقوم SDL_mutexP بإرجاع قيمة حتى يقوم مؤشر الترابط المحظور بفتحه (باستخدام SDL_mutexV). عندما يتم استدعاء كائن المزامنة مرة أخرى ، يجب استدعاء SDL_mutexV (المعروف أيضًا باسم SDL_UnlockMutex) عددًا متساويًا من المرات لإعادة كائن المزامنة إلى حالة غير مؤمنة. إرجاع 0 عند النجاح أو -1 عند الخطأ.

int SDL_UnlockMutex(SDL_Mutex *mutex)

إلغاء تأمين Mutex.

int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)

تعمل هذه الوظيفة على فتح وحدة الصوت بالمعلمات المطلوبة وإرجاع 0 في حالة نجاحها ، ووضع معلمات الأجهزة الفعلية في البنية التي تشير إليها في النهاية. إذا تم استلام قيمة فارغة (NULL) ، فسيتم ضمان أن تكون البيانات الصوتية التي تم تمريرها إلى وظيفة رد الاتصال تحتوي على التنسيق المطلوب ، وإذا لزم الأمر ، سيتم تحويلها تلقائيًا إلى تنسيق صوت الأجهزة. ترجع هذه الوظيفة -1 إذا تعذر فتح جهاز الصوت أو تعذر تكوين دفق الصوت.

لفتح جهاز صوتي ، تحتاج إلى إنشاء SDL_AudioSpec المطلوب. ثم تحتاج لملء هذا الهيكل بالمواصفات الصوتية المطلوبة.

-> freq المطلوب: تردد الصوت المطلوب في العينات في الثانية.
التنسيق المطلوب > تنسيق الصوت المطلوب (راجع SDL_AudioSpec).
القنوات المرغوبة: القنوات المطلوبة (1 للأحادية ، 2 للاستريو ، 4 للصوت المحيطي ، 6 للصوت المحيطي مع توسيط و LFE).
العينات المطلوبة > حجم المخزن الصوتي المطلوب في العينات. يجب أن يكون هذا الرقم قوة من اثنين ويمكن ضبطه بواسطة برنامج تشغيل الصوت إلى قيمة أكثر ملاءمة للأجهزة. تتراوح القيم المثلى من 512 إلى 8192 شاملة ، حسب التطبيق وسرعة المعالج. تؤدي القيم الأصغر إلى أوقات استجابة أسرع ، ولكن يمكن أن تؤدي إلى أداء ضعيف إذا كان التطبيق يقوم بمعالجة ثقيلة ولا يمكنه ملء المخزن المؤقت الصوتي في الوقت المناسب. تتكون عينة الاستريو من القنوات اليمنى واليسرى بترتيب LR. يرجى ملاحظة أن عدد العينات يرتبط مباشرة بالوقت باستخدام الصيغة التالية: ms = (عينات * 1000) / تكرار .
المطلوب> رد الاتصال : يجب تعيينه على وظيفة سيتم استدعاؤها عندما تكون وحدة الصوت جاهزة لاستقبال بيانات إضافية. يتم إرسال مؤشر المخزن الصوتي والطول بالبايت من المخزن المؤقت الصوتي. عادةً ما يتم تنفيذ هذه الوظيفة في سلسلة محادثات منفصلة ، وبالتالي فمن الضروري حماية هياكل البيانات التي تصل إليها عن طريق استدعاء SDL_LockAudio و SDL_UnlockAudio في التعليمات البرمجية. النموذج الأولي لرد الاتصال هو رد باطل ( void * userdata ، Uint8 * stream ، int len ) . بيانات المستخدم - مؤشر مخزّن في حقل بيانات المستخدم SDL_AudioSpec. مجرىهو مؤشر إلى المخزن المؤقت الصوتي الذي تريد ملؤه بالمعلومات ، و len هو طول المخزن المؤقت الصوتي بالبايت.
مطلوب> بيانات المستخدم : يتم تمرير هذا المؤشر كمعلمة أولى لوظيفة رد الاتصال.

يقرأ SDL_OpenAudio هذه الحقول من بنية SDL_AudioSpec المطلوبة التي تم تمريرها إلى الوظيفة ويحاول العثور على التكوين الصوتي الذي يطابق رغبتك. كما ذكر أعلاه ، إذا كانت المعلمة الناتجة هي NULL ، يتم تحويل SDL من إعدادات الصوت المطلوبة إلى إعدادات الجهاز أثناء التشغيل.

إذا تم إرجاع NULL ، فإن SDL_AudioSpec المطلوب هو مواصفات العمل الخاصة بك ، وإلا يصبح SDL_AudioSpec الناتج مواصفات عمل ، ويمكن حذف المواصفات المطلوبة. يتم استخدام البيانات في مواصفات العمل عند إنشاء SDL_AudioCVT لتحويل البيانات التي تم تنزيلها إلى تنسيق المعدات.

يحسب SDL_OpenAudio حقول الحجم والصمت لكل من المواصفات المرغوبة والمواصفات الناتجة. يخزن حقل الحجم الحجم الكلي لمخزن الصوت المؤقت بالبايت ، بينما يخزن الصمت القيمة المستخدمة لتمثيل الصمت في المخزن المؤقت الصوتي

يبدأ جهاز الصوت في التشغيل بصمت عندما يكون مفتوحًا ويجب تشغيله للتشغيل من خلال الاتصال بـ SDL_PauseAudio (0) عندما تكون مستعدًا لاستدعاء وظيفة رد الاتصال الصوتي. نظرًا لأن برنامج تشغيل الصوت يمكنه تغيير الحجم المطلوب لمخزن الصوت المؤقت ، يجب تحديد أي مخازن محلية للخلاط بعد فتح جهاز الصوت.

void SDL_PauseAudio(int pause_on)

تقوم هذه الوظيفة بإيقاف معالجة رد الاتصال الصوتي مؤقتًا وإيقافها. يجب أن يتم استدعاؤه باستخدام pause_on = 0 بعد فتح جهاز الصوت لبدء تشغيل الصوت. يسمح لك ذلك بتهيئة البيانات لوظيفة رد الاتصال بأمان بعد فتح وحدة الصوت. سيتم تسجيل الصمت على جهاز الصوت أثناء وقفة.

int SDL_PushEvent(SDL_Event *event)

قائمة انتظار حدث يتم استخدامها بالفعل كقناة اتصال ثنائية الاتجاه. لا يمكن فقط قراءة الأحداث من قائمة الانتظار ، ولكن يمكن للمستخدم أيضًا وضع الأحداث الخاصة به فيها. الحدث عبارة عن مؤشر لبنية الحدث الذي تريد وضعه في قائمة الانتظار. يتم نسخ الحدث إلى قائمة الانتظار ، ويمكن للمتصل إدارة الذاكرة المشار إليها بعد إرجاع SDL_PushEvent. هذه الوظيفة موجهة للخيط ويمكن استدعاؤها بأمان من خيوط أخرى. إرجاع 0 في حالة نجاح أو -1 إذا تعذر إرسال الحدث.

int SDL_WaitEvent(SDL_Event *event)

ينتظر إلى أجل غير مسمى للحدث المتاح التالي ، ويعيد 0 إذا حدث خطأ أثناء انتظار الأحداث ، 1 خلاف ذلك. إذا لم يكن الحدث فارغًا ، فسيتم إزالة الحدث التالي من قائمة الانتظار وتخزينه في هذه المنطقة.

void SDL_Quit()

تعطيل كافة أنظمة SDL الفرعية وتحرير الموارد المخصصة لها. يجب أن يتم استدعاء هذا دائمًا قبل الخروج.

SDL_Surface *SDL_SetVideoMode(int width, int height, int bitsperpixel, Uint32 flags)

إعداد وضع الفيديو مع العرض والارتفاع وبت وحدات البكسل المحددة. بدءًا من الإصدار 1.2.10 من SDL ، إذا كان العرض والارتفاع يساوي 0 ، فسيستخدم عرض وارتفاع وضع الفيديو الحالي (أو وضع سطح المكتب إذا لم يتم تعيين الوضع). إذا كان bitsperpixel يساوي 0 ، فسيتم معاملته على أنه بتات العرض الحالية لكل بكسل. معلمة الإشارات هي نفسها حقل الإشارات لبنية SDL_Surface. أو مزيج من القيم التالية:

SDL_SWSURFACE - إنشاء سطح فيديو في ذاكرة النظام.
SDL_HWSURFACE - إنشاء سطح فيديو في ذاكرة الفيديو.
SDL_ASYNCBLIT - تمكين استخدام التحديثات غير المتزامنة على سطح الشاشة. عادة ما يؤدي ذلك إلى إبطاء العمل على أجهزة الكمبيوتر ذات المعالج الواحد ، ولكن يمكن أن يزيد السرعة في أنظمة SMP.
SDL_ANYFORMAT - عادةً ، إذا كان سطح الفيديو الذي يحتوي على وحدات البت المطلوبة لكل بكسل (bpp - من وحدات البت لكل بكسل) غير متوفر ، فستقوم SDL بمحاكاة الفيديو بسطح مظلل. يمنع تمرير SDL_ANYFORMAT ذلك ويجبر SDL على استخدام سطح الفيديو ، بغض النظر عن عمق وحدات البكسل.
SDL_HWPALETTE - توفير وصول خاص لـ SDL إلى اللوحة. بدون هذه العلامة ، لا يمكنك دائمًا الحصول على الألوان التي تطلبها باستخدام SDL_SetColors أو SDL_SetPalette.
SDL_DOUBLEBUF - تمكين التخزين المؤقت المزدوج للأجهزة ؛ صالح فقط مع SDL_HWSURFACE. سيؤدي استدعاء SDL_Flip إلى عكس المخازن المؤقتة وتحديث الشاشة. ستتم جميع عمليات الرسم على سطح غير معروض حاليًا. إذا تعذر تمكين التخزين المؤقت المزدوج ، فسيقوم SDL_Flip ببساطة بتنفيذ SDL_UpdateRect ملء الشاشة.
SDL_FULLSCREEN SDL - حاول استخدام وضع ملء الشاشة. إذا كان تغيير دقة الجهاز غير ممكن (لأي سبب) ، فسيتم استخدام الدقة الأعلى التالية ، وسيتم توسيط نافذة العرض على خلفية سوداء.
SDL_OPENGL - قم بإنشاء سياق عرض OpenGL. من المفترض أن سمات فيديو OpenGL ذات SDL_GL_SetAttribute محددة مسبقًا.
SDL_OPENGLBLIT - قم بإنشاء سياق عرض OpenGL ، كما هو موضح أعلاه ، ولكن مع السماح بعمليات التصفية العادية. يمكن أن يحتوي سطح الشاشة (ثنائي الأبعاد) على قناة ألفا ، ويجب استخدام SDL_UpdateRects لتحديث تغييرات سطح الشاشة. ملحوظة. يتم حفظ هذا الخيار فقط للتوافق وسيتم إزالته في الإصدارات المستقبلية. غير مستحسن للاستخدام في التعليمات البرمجية الجديدة.
SDL_RESIZABL -إنشاء نافذة قابلة لتغيير الحجم. عندما يقوم المستخدم بتغيير حجم النافذة ، يتم إنشاء حدث SDL_VIDEORESIZE ، ويمكن استدعاء SDL_SetVideoMode مرة أخرى بحجم جديد.
SDL_NOFRAME إن أمكن ، يفرض SDL_NOFRAME SDL على إنشاء إطار بدون عنوان أو مؤطر. يتم تعيين هذه العلامة تلقائيًا في وضع ملء الشاشة.
ملحوظة. بغض النظر عن أعلام SDL_SetVideoMode التي يمكن أن تلبي ، يتم تعيينها في عنصر الإشارات على السطح الذي تم إرجاعه.
ملحوظة. يستخدم Bit pixel 24 تمثيلاً معبأًا يبلغ 3 بايتات لكل بكسل. للحصول على وضع 4 بايت الأكثر شيوعًا لكل بكسل ، استخدم بكسل 32 بت. ومن الغريب أن كلا 15 و 16 سيطلبان 2 بايت لكل وضع بكسل ، ولكن بتنسيقات بكسل مختلفة.
ملحوظة. استخدم SDL_SWSURFACE إذا كنت تخطط لإجراء معالجة منفصلة للبكسل أو سحب الأسطح باستخدام قنوات ألفا وتتطلب معدل إطارات مرتفع. عند استخدام أسطح الأجهزة (SDL_HWSURFACE) ، تنسخ SDL الأسطح من ذاكرة الفيديو إلى ذاكرة النظام عندما تقوم بقفلها والعكس بالعكس عند فتحها. هذا يمكن أن يؤدي إلى انخفاض كبير في الأداء. (ضع في اعتبارك أنه يمكنك الاستعلام عن سطح الجهاز ولكن لا يزال لديك سطح برنامج. يمكن للعديد من الأنظمة الأساسية توفير سطح جهاز فقط عند استخدام SDL_FULLSCREEN.) يُفضل استخدام SDL_HWSURFACE عندما يمكن أيضًا تخزين الأسطح التي ستومض في ذاكرة الفيديو.
ملحوظة. إذا كنت تريد التحكم في الموضع على الشاشة عند إنشاء سطح النافذة ، يمكنك القيام بذلك عن طريق تعيين متغيرات البيئة "SDL_VIDEO_CENTERED = المركز" أو "SDL_VIDEO_WINDOW_POS = x، y". يمكنك تثبيتها عبر SDL_putenv.

القيمة المعادة : سطح الإطار المؤقت أو NULL في حالة الفشل. يتم تحرير السطح الذي تم إرجاعه بواسطة SDL_Quit ويجب ألا يتم تحريره بواسطة المتصل.
ملحوظة. تتضمن هذه القاعدة مكالمات متتالية إلى SDL_SetVideoMode (أي تغيير الحجم) - سيتم تحرير السطح الحالي تلقائيًا.






الملحق 2. هياكل البيانات



AVCodecContext

جميع المعلومات حول برنامج الترميز من الدفق ، من AVStream-> برنامج الترميز. بعض السمات المهمة:

AVRational time_base : عدد الإطارات في الثانية
int sample_rate : عينات لكل قناة
int ثانية : عدد القنوات راجع القائمة الكاملة (مثيرة للإعجاب للغاية) هنا ( أرشيف الويب ، لأن الرابط الأصلي غير موجود بالفعل ). يتم استخدام العديد من المعلمات بشكل أساسي للتشفير ، وليس لفك التشفير.



AVFormatContext

حقول البيانات:

const AVClass * av_class
AVInputFormat * iformat
AVOutputFormat * oformat
void * priv_data :
ByteIOContext pb : تستخدم لمعالجة الملفات ذات المستوى المنخفض.
int غير موقعة nb_streams : عدد مؤشرات الترابط في الملف.
تيارات AVStream * [MAX_STREAMS] : يتم تخزين البيانات لكل دفق هنا.
اسم الملف شار [1024]: ولكن ماذا عنه بدونه (في الأصل - دوه ).

معلومات الملف:
int64_t timestamp :
char title [512]:
شار المؤلف [512]:
شار حقوق التأليف والنشر [512]:
شار تعليق [512]:
شار ألبوم [512]:
كثافة العمليات السنة :
كثافة العمليات المسار :
شار النوع [32]:

الباحث ctx_flags :
القيم الممكنة هي AVFMT_NOFILE، AVFMT_NEEDNUMBER، AVFMT_SHOW_IDS، AVFMT_RAWPICTURE، AVFMT_GLOBALHEADER ، AVFMT_NOTIMESTAMPS ، AVFMT_GENERIC_INDEX
AVPacketList * packet_buffer : هذا المخزن المؤقت مطلوب فقط عندما يتم تخزين الحزم مؤقتًا بالفعل ولكن لا يتم فك تشفيرها ، على سبيل المثال ، لتلقي معلمات الترميز في تدفقات mpeg.
int64_tstart_time : عند فك التشفير: موضع الإطار الأول للمكون ، في أجزاء من الثانية AV_TIME_BASE. لا تقم أبدًا بتعيين هذه القيمة مباشرة: يتم استنتاجها من قيم AVStream.
مدة int64_t: فك التشفير : مدة الدفق ، بأجزاء AV_TIME_BASE. لا تقم أبدًا بتعيين هذه القيمة مباشرة: يتم استنتاجها من قيم AVStream.
int64_t file_size : إجمالي حجم الملف ، 0 إذا كان غير معروف.
int bit_rate : فك التشفير: إجمالي معدل البت للتدفق بالبتات / ثانية ، 0 إذا لم يكن متاحًا. لا تقم أبدًا بتعيينها مباشرة إذا كان file_size والمدة المعروفة في ffmpeg يمكنها حسابها تلقائيًا.
AVStream * cur_st
const uint8_t * cur_ptr
int cur_len
AVPacket cur_pkt :
int64_t data_offset :
int index_built : إزاحة الحزمة الأولى.
int mux_rate :
int packet_size :
int preload :
int max_delay :
int loop_output : عدد حلقات الإخراج في التنسيقات المدعومة.
int flags :
int loop_input :
int غير الموقعة int probize : فك التشفير: حجم بيانات العينة ؛ لا تستخدم في الترميز.
int max_analyze_duration : المدة القصوى بالوحدات AV_TIME_BASE التي يجب خلالها تحليل بيانات الإدخال في av_find_stream_info ()
const uint8_t * key :
int keylen :

AVIOContext

سياق الإدخال / الإخراج للوصول إلى الموارد.

const AVClass * av_class : فئة للإعدادات الخاصة.
علامة تشار * العازلة : بداية المخزن المؤقت.
int buffer_size : الحد الأقصى لحجم المخزن المؤقت.
char * buf_ptr : الموقع الحالي في المخزن المؤقت.
char * buf_end غير الموقعة : يمكن أن تكون البيانات أصغر من المخزن المؤقت + buffer_size إذا أعادت وظيفة القراءة بيانات أقل مما هو مطلوب ، على سبيل المثال.
void * opaque : تمرير مؤشر خاص للقراءة / الكتابة / البحث / ...
int (* read_packet) (void * opaque، uint8_t * buf، int buf_size) :
int (* write_packet) (void * opaque، uint8_t * buf، int buf_size ) :
int64_t (* تسعى) (باطل * معتم ، int64_t offset ، int
whence ) : int64_t pos : موضع في ملف المخزن المؤقت الحالي.
int must_flush : صحيح إذا كان يجب إعادة تعيين البحث التالي.
int eof_reached : صحيح إذا تم الوصول إلى نهاية الملف.
int write_flag : صحيح إذا كان مفتوحًا للكتابة.
int max_packet_size :
المجموع الاختباري الطويل غير الموقَّع :
char
غير الموقَّع * checkum_ptr : الطويل غير الموقَّع (* update_checksum) (المجموع الاختباري الطويل غير الموقع ، const uint8_t * buf ، الحجم الدولي غير الموقَّع) :
خطأ int : يحتوي على رمز الخطأ أو 0 إذا لم يحدث خطأ.
int (* read_pause) (باطل * معتم ، int pause): إيقاف التشغيل مؤقتًا أو استئنافه لبروتوكولات دفق الشبكة ، على سبيل المثال.
int64_t (* read_seek) (void * opaque، int stream_index، int64_t timestamp، int flags) : ابحث عن الطابع الزمني المحدد في الدفق باستخدام فهرس stream_index المحدد.
int الباحث : مزيج من إشارات AVIO_SEEKABLE_ أو 0 عندما لا يكون الدفق قابلاً للبحث.
int64_t maxsize : الحد الأقصى لحجم الملف المستخدم للحد من التحديد. هذا الحقل داخلي لـ libavformat ، ويحظر الوصول إليه من الخارج.
int direct : يجب تنفيذ avio_read و avio_write مباشرة كلما أمكن ذلك ، وعدم المرور عبر المخزن المؤقت ، وسيتصل avio_seek دائمًا بوظيفة البحث الرئيسية مباشرةً.
int64_t bytes_read: إحصائيات قراءة البايت هذا الحقل داخلي لـ libavformat وتم رفض الوصول الخارجي.
int تسعى_count : إحصائيات البحث. هذا الحقل داخلي لـ libavformat ، ويحظر الوصول إليه من الخارج.
int writeout_count : كتابة الإحصائيات. هذا الحقل داخلي لـ libavformat ، ويحظر الوصول إليه من الخارج.
int orig_buffer_size : حجم المخزن المؤقت الأصلي المستخدم داخليًا بعد التحقق وتوفير عودة لإعادة تعيين حجم المخزن المؤقت. هذا الحقل داخلي لـ libavformat ، ويحظر الوصول إليه من الخارج.

AVDictionary

تستخدم لتمرير المعلمات إلى ffmpeg.

العدد
الداخلي : AVDictionaryEntry * elems :

AVDictionaryEntry

تستخدم لتخزين إدخالات القاموس في AVDictionary.

char * ket :
char * value :

AVFrame

تعتمد هذه البنية على نوع برنامج الترميز ، وبالتالي يتم تحديدها ديناميكيًا. ومع ذلك ، هناك خصائص وطرق شائعة لهذا الهيكل:

uint8_t * data [4] :
int lineize [4] : خطوة المعلومات.
uint8_t * base [4] :
int key_frame :
int pict_type :
int64_t pts : هذه ليست النقاط التي تتوقعها عند فك التشفير.
int coded_picture_number :
int display_picture_number :
int quality :
int age :
int reference :
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 :
خطأ بيانات المستخدم uint64_t [4] :
int type :
int تكرار_الصورة : يرشدك لتكرار الصورة بعدد محدد من المرات.
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

الهيكل الذي يتم فيه تخزين بيانات الحزمة الأولية. يجب نقل هذه البيانات إلى avcodec_decode_audio2 أو avcodec_decode_video لاستلام إطار.

int64_t pts : الطابع الزمني للعرض بوحدات time_base.
int64_t dts : طابع زمني لفك ضغط الوحدات الزمنية.
بيانات uint8_t * : البيانات الأولية.
حجم int : حجم البيانات.
int stream_index : الدفق الذي جاء منه AVPacket ، بناءً على الكمية في AVFormatContext.
علامات int : يتم تعيين PKT_FLAG_KEY إذا كانت الحزمة إطار مفتاحي.
المدة الزمنية : مدة العرض بالوحدات الزمنية (0 إذا لم تكن متاحة)
void (* تدمير) (هيكل AVPacket *) : وظيفة تحرير الموارد لهذه الحزمة (الافتراضي هو av_destruct_packet).
void * priv :
int64_t pos : موضع بايت في الدفق ، -1 إذا كان غير معروف.

AVPacketList

قائمة مرتبطة بسيطة للحزم.

AVPacket pkt :
AVPacketList * التالي :

AVPicture

هذه البنية هي بالضبط نفس عنصري بيانات AVFrame الأولين ، لذلك يتم تجاهلها غالبًا. يشيع استخدامها في وظائف SWS.

uint8_t * data [4] :
int lineize [4] : عدد وحدات البايت في السلسلة.

AVRational

هيكل بسيط لتمثيل الأرقام العقلانية.

عدد صحيح : البسط.
int den : المقام.

AVStream

هيكل الدفق. من المحتمل أن تستخدم هذه المعلومات في برنامج الترميز في أغلب الأحيان.

مؤشر كثافة العمليات :
معرف كثافة العمليات :
AVCodecContext * الترميز :
AVRational r_frame_rate :
الفراغ * priv_data :
codec_info_duration int64_t :
codec_info_nb_frames الباحث :
نقطة AVFrac :
time_base AVRational :
pts_wrap_bits الباحث :
كثافة stream_copy :
AVDiscard على التعداد وتجاهل : يمكنك اختيار الحزم التي سوف يكون بعيدا لأنها لا تحتاج في تعدد الإرسال.
تعويم الجودة :
int64_t start_time :
int64_t المدة:
شار لغة [4] :
الباحث need_parsing : 1 -> يحتاج تحليل كامل، 2 -> رؤوس في التحليل فقط، من دون إعادة التعبئة
AVCodecParserContext * محلل :
int64_t cur_dts :
كثافة last_IP_duration :
last_IP_pts int64_t :
AVIndexEntry * index_entries :
nb_index_entries الباحث :
عدد صحيح index_entries_allocated_size غير موقعة :
int64_t nb_frames : عدد الإطارات في هذا الدفق (إذا كان معروفًا) أو 0
int64_t pts_buffer [MAX_REORDER_DELAY + 1] :

ByteIOContext

هيكل يقوم بتخزين معلومات منخفضة المستوى حول ملف فيلم.

char * buffer :
int buffer_size : unigned
char * buf_ptr : charigned b
* 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 (* تسعى) (باطل * معتم ، offset_t offset ، int
whence ) : offset_t pos :
int must_flush :
int eof_reached :
int write_flag :
int is_streamed :
int max_packet_size :
المجموع الاختباري الطويل غير الموقَّع :
char
غير الموقَّع * checkum_ptr
: طويل غير موقَّع (* update_checksum) (المجموع الاختباري الطويل غير الموقَّع: const uint8_t * buf ، الحجم int غير الموقَّع) :
خطأ int : يحتوي على رمز الخطأ أو 0 في حالة عدم حدوث خطأ.

SDL_AudioSpec

يستخدم لوصف تنسيق بعض البيانات الصوتية.

freq : تردد الصوت في العينات في الثانية.
التنسيق : تنسيق البيانات الصوتية.
القنوات : عدد القنوات: 1 - أحادي ، 2 - استريو ، 4 محيطي ، 6 محاط بتوسيط
وصمت LFE : قيمة صمت المخزن المؤقت الصوتي (محسوب).
العينات : حجم المخزن الصوتي في العينات.
الحجم : حجم المخزن المؤقت الصوتي بالبايت (المحسوب).
رد الاتصال (..) : وظيفة رد الاتصال لملء المخزن المؤقت الصوتي.
بيانات المستخدمين : مؤشر إلى بيانات المستخدم التي يتم تمريرها إلى وظيفة رد الاتصال.

قيم التنسيق التالية

صالحة : AUDIO_U8 - ​​عينات غير موقعة من 8 بت.
AUDIO_S8 - عينات 8 بت موقعة.
AUDIO_U16 أو AUDIO_U16LSB - غير مدعوم من قبل جميع الأجهزة (ترتيب بايت منخفض 16 بت غير موقع).
AUDIO_S16L أو AUDIO_S16LS - غير مدعوم من قبل جميع الأجهزة (16 بت بترتيب بايت قديم)
AUDIO_U16MSB - غير مدعوم من قبل جميع الأجهزة (غير موقعة 16 بت كبيرة).
AUDIO_S16MS - غير مدعوم من جميع الأجهزة (16 بت بترتيب بايت عالي).
AUDIO_U16SYS: إما AUDIO_U16LSB أو AUDIO_U16MSB - اعتمادًا على معالج الأجهزة.
AUDIO_S16SYS: إما AUDIO_S16LSB أو AUDIO_S16MSB - اعتمادًا على معالج الأجهزة.

SDL_Event

الهيكل الأساسي للأحداث.

النوع : نوع الحدث.
active : حدث التنشيط (راجع SDL_ActiveEvent).
المفتاح : حدث لوحة المفاتيح (راجع SDL_KeyboardEvent).
الحركة : حدث حركة الماوس (راجع SDL_MouseMotionEvent).
الزر : حدث النقر بالماوس (راجع SDL_MouseButtonEvent).
jaxis : حدث حركة محور عصا التحكم (راجع SDL_JoyAxisEvent).
jball : حدث حركة كرة التتبع بعصا التحكم (انظر SDL_JoyBallEvent).
jhat : حدث حركة رأس عصا التحكم (راجع SDL_JoyHatEvent).
jbutton : حدث الضغط على زر عصا التحكم (راجع SDL_JoyButtonEvent).
تغيير الحجم: حدث تغيير حجم نافذة التطبيق (راجع SDL_ResizeEvent).
فضح : حدث فتح نافذة التطبيق (انظر SDL_ExposeEvent).
إنهاء : حدث طلب الخروج من التطبيق (راجع SDL_QuitEvent).
المستخدم : حدث المستخدم (راجع SDL_UserEvent).
syswm : حدث مدير نوافذ غير محدد (راجع SDL_SysWMEvent).

فيما يلي أنواع الأحداث. راجع وثائق SDL لمزيد من المعلومات :.

SDL_ACTIVEEVENT SDL_ActiveEvent
SDL_KEYDOWN / وUP SDL_KeyboardEvent
SDL_MOUSEMOTION SDL_MouseMotionEvent
SDL_MOUSEBUTTONDOWN / وUP SDL_MouseButtonEvent
SDL_JOYAXISMOTION SDL_JoyAxisEvent
SDL_JOYBALLMOTION SDL_JoyBallEvent
SDL_JoyHatEvent SDL_JOYHATMOTION
SDL_JOYBUTTONDOWN / UP
SDL_JoyButtonEvent
SDL_VIDEORESIZE SDL_ResizeEvent
SDL_VIDEOEXPOSE SDL_ExposeEvent
SDL_Quit SDL_Vent
SDL_Vent SDL_VentSLV

SDL_Overlay

تراكب YUV.

التنسيق : تنسيق التراكب (انظر أدناه).
w، h : عرض / ارتفاع التراكب.
الطائرات : عدد خطط التراكب. عادة ما تكون 1 أو 3
درجات : مجموعة من المسافات البادئة ، واحدة لكل خطة. المسافة البادئة هي طول السلسلة بالبايت.
بكسل : صفيف من مؤشرات البيانات لكل خطة. يجب قفل التراكب قبل استخدام هذه المؤشرات.
hw_overlay : اضبط على 1 إذا كان التراكب معجل للأجهزة.

SDL_Rect

المنطقة المستطيلة.

Sint16 x، y : موضع الزاوية اليسرى العليا من المستطيل.
Uint16 w ، h : عرض المستطيل وارتفاعه.

يحدد SDL_Rect مساحة مستطيلة من وحدات البكسل. يتم استخدامه بواسطة SDL_BlitSurface لتحديد مناطق الخداع وبعض ميزات الفيديو الأخرى.

SDL_Surface

الهيكل البياني للجانب الخارجي (السطح).

أعلام Uint32 : أعلام stotrons الخارجية. للقراءة فقط.
تنسيق SDL_PixelFormat * : للقراءة فقط.
int w، h : العرض والارتفاع. للقراءة فقط.
خطوة Uint16 : خطوة. للقراءة فقط.
void * pixels : مؤشر لبيانات البيكسل الفعلية. للتسجيل فقط.
SDL_Rect clip_rect : المستطيل الموجود خارج المقطع. للقراءة فقط.
int refcount : يستخدم لتخصيص الذاكرة. في الغالب للقراءة.
يحتوي هذا الهيكل أيضًا على حقول خاصة غير معروضة هنا.

يمثل SDL_Surface مساحة من الذاكرة "الرسومية" التي يمكن رسمها. يتم إرجاع إطار المخزن المؤقت للفيديو على أنه SDL_Surface باستخدام SDL_SetVideoMode و SDL_GetVideoSurface. الحقول w و h هي قيم تمثل عرض وارتفاع السطح بالبكسل. يعتبر حقل البكسل مؤشرًا لبيانات البكسل الفعلية. ملاحظة: يجب تأمين السطح (عبر SDL_LockSurface) قبل الوصول إلى هذا الحقل. الحقل clip_rect هو المستطيل المقصوص الذي تم تعيينه بواسطة SDL_SetClipRect.

يدعم حقل العلم قيم OR التالية:

SDL_SWSURFACE - يتم تخزين الجزء الخارجي في ذاكرة النظام.
SDL_HWSURFACE - يتم تخزين الجانب الخارجي في ذاكرة الفيديو.
SDL_ASYNCBLIT - يستخدم الخارج الوهج غير المتزامن ، إن أمكن.
SDL_ANYFORMAT - يسمح بأي تنسيق بكسل (سطح العرض).
SDL_HWPALETTE - يحتوي السطح على لوحة حصرية.
SDL_DOUBLEBUF - سطح مخزَّن مزدوج (سطح عرض).
SDL_FULLSCREEN - سطح ملء الشاشة (سطح عرض).
SDL_OPENGL - السطح به سياق OpenGL (سطح العرض).
SDL_OPENGLBLIT - يدعم السطح المفتوح OpenGL (سطح العرض). ملحوظة. هذا الخيار للتوافق فقط ولا يوصى به للرمز الجديد.
SDL_RESIZABLE - يمكن تغيير الحجم لسطح (سطح عرض).
SDL_HWACCEL - يستخدم السطح السطحي تسريع الأجهزة.
SDL_SRCCOLORKEY - السطحية تستخدم التبييض اللوني.
SDL_RLEACCEL - تسريع عملية مزج الألوان باستخدام RLE.
SDL_SRCALPHA - يستخدم Surface Blyth مزج ألفا.
SDL_PREALLOC - يستخدم السطح ذاكرة مخصصة مسبقًا.

SDL_Thread

هذا الهيكل مستقل عن النظام ، وربما لا تحتاج إلى استخدامه. انظر src / thread / sdl_thread_c.h في شفرة المصدر لمزيد من المعلومات.

SDL_cond

هذا الهيكل مستقل عن النظام ، وربما لا تحتاج إلى استخدامه. انظر src / thread / <system> /SDL_syscond.c في كود المصدر لمزيد من المعلومات.

SDL_mutex

هذا الهيكل مستقل عن النظام ، وربما لا تحتاج إلى استخدامه. انظر src / thread / <system> /SDL_sysmutex.c في كود المصدر لمزيد من المعلومات.






روابط


دروس FFmpeg و SDL أو كيفية كتابة مشغل فيديو في أقل من 1000

سطر FFmpeg ، SDL FFmpeg HomePage SDL HomePage











اقرأ أيضا على مدونة شركة إديسون:


FFmpeg دليل ليباف

All Articles