Merge pull request #8 from DevonCrawford/feature/randomSplice

Feature/random splice
This commit is contained in:
Devon Crawford
2019-03-04 01:53:03 -05:00
committed by GitHub
15 changed files with 860 additions and 95 deletions

View File

@@ -116,6 +116,11 @@ OBJS_BASE= VideoContext Clip ClipDecode OutputContext Timebase \
$(DBE)test-sequence-encode: $$(call EXE_OBJS,$$@,$(OBJS_BASE))
$(LINK_EXE)
OBJS_BASE=Sequence LinkedListAPI Clip Util VideoContext Timebase \
OutputContext SequenceEncode SequenceDecode ClipDecode
$(DBE)random-splice: $$(call EXE_OBJS,$$@,$(OBJS_BASE))
$(LINK_EXE)
# $(1) = name of exe
# $(2) = the list of basename object files that the executable needs to run, without .o
define EXE_OBJS

358
examples/random-splice.c Normal file
View File

@@ -0,0 +1,358 @@
#include "RandomSplice.h"
/**
* valgrind --leak-check=yes bin/examples/random-splice test-resources/sequence/out7.mov 30 48000 test-resources/sequence/ 300 80 40
*/
int main(int argc, char **argv) {
if(argc < 8) {
printf("usage: %s output_file fps sample_rate source_dir duration cut_len_avg cut_len_var\n", argv[0]);
printf("\nExplanation\n------------\n");
printf("output_file(string): output filename of encoded edit (ex. out.mov)\n");
printf("fps(int): frames per second to use in sequence. All frame parameters are based on this (ex. 30 for 30fps)\n");
printf("sample_rate(int): audio sample rate (ex. 48000 for 48kHz)\n");
printf("source_dir(string): directory of raw video files that will be used in the edit\n");
printf("duration(int): duration of output file (in frames - fps defined above)\n");
printf("cut_len_avg(int): average length of cuts (in frames)\n");
printf("cut_len_var(int): variability of average cuts used by the random number generator for max and min range\n");
return -1;
}
RandSpliceParams par;
strcpy(par.output_file, argv[1]);
sscanf(argv[2], "%lf", &(par.fps));
par.sample_rate = atoi(argv[3]);
strcpy(par.source_dir, argv[4]);
par.duration = atoi(argv[5]);
par.cut_len_avg = atoi(argv[6]);
par.cut_len_var = atoi(argv[7]);
par.pick_frames_recur = 0;
int num_files, ret = 0;
char **files = get_filenames_in_dir(par.source_dir, &num_files);
srand(time(NULL));
Sequence orig_seq, new_seq;
init_sequence_cmp(&orig_seq, par.fps, par.sample_rate, &list_compare_clips_sequential);
init_sequence_cmp(&new_seq, par.fps, par.sample_rate, &list_compare_clips_sequential);
ret = add_files(&orig_seq, files, num_files);
if(ret < 0) {
fprintf(stderr, "failed to add files\n");
return ret;
}
char *str = print_sequence(&orig_seq);
printf("====== ORIG_SEQ =====\n%s\n", str);
free(str);
str = NULL;
ret = random_edit(&orig_seq, &new_seq, &par);
if(ret < 0) {
fprintf(stderr, "random_edit() error: Failed to finish edit\n");
goto end;
}
str = print_sequence(&new_seq);
printf("====== NEW_SEQ =====\n%s\n", str);
free(str);
str = NULL;
if(new_seq.clips.head != NULL) {
// output parameters
OutputParameters op;
VideoOutParams vp;
AudioOutParams ap;
Clip *clip1 = (Clip *) (new_seq.clips.head->data);
set_video_out_params(&vp, clip1->vid_ctx->video_codec_ctx);
vp.codec_id = AV_CODEC_ID_NONE;
vp.bit_rate = -1;
set_audio_out_params(&ap, clip1->vid_ctx->audio_codec_ctx);
if(set_output_params(&op, par.output_file, vp, ap) < 0) {
fprintf(stderr, "Failed to set output params\n");
goto end;
}
ret = write_sequence(&new_seq, &op);
if(ret < 0) {
fprintf(stderr, "Failed to write new sequence to output file[%s]\n", op.filename);
goto end;
}
}
end:
// print_str_arr(files, num_files);
free_str_arr(&files, num_files);
free_sequence(&orig_seq);
free_sequence(&new_seq);
}
/**
* Make a random edit. This function calls itself recursively to continue making
* cuts until the output sequence is our desired length
* @param os original sequence (input)
* @param ns new sequence (output)
* @param par parameters from user to control algorithm
* @return 0 on success (fully edited sequence), < 0 on fail
*/
int random_edit(Sequence *os, Sequence *ns, RandSpliceParams *par) {
if(par->cut_len_var > par->cut_len_avg) {
fprintf(stderr, "random_edit() error: cut_len_var[%d] must be less than cut_len_avg[%d]\n", par->cut_len_var, par->cut_len_avg);
return -1;
}
printf("get_sequence_duration: %ld\n", get_sequence_duration(ns));
printf("par->duration: %ld\n", par->duration);
printf("if %d\n", get_sequence_duration(ns) > par->duration);
if(get_sequence_duration(ns) > par->duration) {
return 0;
}
int ret = random_cut(os, ns, par);
if(ret < 0) {
return ret;
}
return random_edit(os, ns, par);
}
/**
* Make a random cut from original sequence and place that clip into new sequence
* @param os original sequence (input)
* @param ns new sequence (output)
* @param par parameters from user to control algorithm
* @return >= 0 on success
*/
int random_cut(Sequence *os, Sequence *ns, RandSpliceParams *par) {
int s, e;
int ret = pick_frames(os, par, &s, &e);
if(ret < 0) {
fprintf(stderr, "random_cut() error: Failed to pick frames\n");
return ret;
}
printf("pick_frames.. s: %d, e: %d\n", s, e);
return cut_remove_insert(os, ns, s, e);
}
/**
* Cut a clip out of original sequence and insert it into new sequence in sorted order
* @param os Original sequence
* @param ns New Sequence
* @param start_index start frame of cut in original sequence
* @param end_index end frame of cut in original sequence
* @return >= 0 on success
*/
int cut_remove_insert(Sequence *os, Sequence *ns, int start_index, int end_index) {
int cut_center_index = start_index + ((end_index - start_index) / 2);
int ret = cut_clip(os, start_index);
if(ret < 0) {
fprintf(stderr, "cut_remove_insert() error: Failed to cut clip at start index[%d]\n", start_index);
return ret;
}
ret = cut_clip(os, end_index);
if (ret < 0) {
fprintf(stderr, "cut_remove_insert() error: Failed to cut clip at end index[%d]\n", end_index);
return ret;
}
Clip *cut = NULL;
find_clip_at_index(os, cut_center_index, &cut);
if(!cut) {
fprintf(stderr, "cut_remove_insert() error: Failed to find clip at cut center index[%d]\n", cut_center_index);
return -1;
}
Clip *cut_copy = alloc_clip(cut->url);
if(cut_copy == NULL) {
fprintf(stderr, "cut_remove_insert() error: Failed to allocate cut_copy\n");
return -1;
}
ret = set_clip_bounds_pts(cut_copy, cut->orig_start_pts, cut->orig_end_pts);
if(ret < 0) {
free_clip(cut_copy);
free(cut_copy);
cut_copy = NULL;
fprintf(stderr, "cut_remove_insert() error: Failed to set clip bounds on cut copy\n");
return ret;
}
ret = sequence_insert_clip_sorted(ns, cut_copy);
if(ret < 0) {
free_clip(cut_copy);
free(cut_copy);
cut_copy = NULL;
fprintf(stderr, "cut_remove_insert() error: failed to add clip[%s] to new sequence\n", cut_copy->url);
return ret;
}
ret = sequence_ripple_delete_clip(os, cut);
if(ret < 0) {
free_clip(cut_copy);
free(cut_copy);
cut_copy = NULL;
fprintf(stderr, "cut_remove_insert() error: Failed to delete clip from original sequence\n");
return ret;
}
return 0;
}
/**
* Randomly pick start and end frames given user parameters
* @param seq Original sequence to pick frames
* @param par user parameters
* @param start_index output index of start frame for cut
* @param end_index output index of end frame for cut
* @return >= 0 on success
*/
int pick_frames(Sequence *seq, RandSpliceParams *par, int *start_index, int *end_index) {
if(par->pick_frames_recur > PICK_FRAMES_RECUR_LIMIT) {
fprintf(stderr, "pick_frames() error: par->pick_frames_recur[%d] > limit[%d]\nThis should never happen\n", par->pick_frames_recur, PICK_FRAMES_RECUR_LIMIT);
return -1;
}
int64_t seq_dur = get_sequence_duration(seq);
if(seq_dur <= 0) {
fprintf(stderr, "pick_frames() error: sequence duration[%ld] is invalid\n", seq_dur);
return -1;
}
int s = rand_range(0, seq_dur - par->cut_len_avg - 1);
int e_var;
if(par->cut_len_var == 0) {
e_var = 0;
} else {
e_var = rand_range((-1)*(par->cut_len_var), par->cut_len_var);
}
int e = s + par->cut_len_avg + e_var;
if(e > seq_dur) {
e = seq_dur;
}
Clip *sc = NULL, *se = NULL;
find_clip_at_index(seq, s, &sc);
find_clip_at_index(seq, e, &se);
if(!sc || !se) {
fprintf(stderr, "pick_frames() error: clip does not exist at start[%d] or end[%d] index\n", s, e);
return -1;
}
// If start and end index do not lie on the same clip.. try again
if(compare_clips_sequential(sc, se) != 0) {
++(par->pick_frames_recur);
return pick_frames(seq, par, start_index, end_index);
}
*start_index = s;
*end_index = e;
par->pick_frames_recur = 0;
return 0;
}
/**
* Generate random number between range in the uniform distribution
* @param min low bound (inclusive)
* @param max high bound (inclusive)
* @return random number between min and max on a uniform distribution
*/
int rand_range(int min, int max) {
int diff = max-min;
return (int) (((double)(diff+1)/RAND_MAX) * rand() + min);
}
/**
* Add all files from string array into sequence!
* @param seq Sequence
* @param files string array of filenames (videos to be added)
* @param num_files number strings in files array
* @return >= 0 on success
*/
int add_files(Sequence *seq, char **files, int num_files) {
int ret;
for(int i = 0; i < num_files; i++) {
Clip *curr = alloc_clip(files[i]);
if(curr == NULL) {
printf("add_files() warning: failed to allocate clip[%s]\n", files[i]);
}
ret = sequence_insert_clip_sorted(seq, curr);
if(ret < 0) {
fprintf(stderr, "add_files() error: failed to add clip[%s] to sequence\n", files[i]);
return ret;
}
printf("added clip[%s] to original sequence\n", files[i]);
}
return 0;
}
/**
* print string array
* @param str string array
* @param rows number of strings
*/
void print_str_arr(char **str, int rows) {
for (int i = 0; i < rows; i++) {
printf("%s\n", str[i]);
}
}
/**
* Free array of strings
* @param str pointer to array of strings
* @param rows number of strings in array
*/
void free_str_arr(char ***str, int rows) {
char **s = *str;
if(s != NULL) {
for(int i = 0; i < rows; i++) {
free(s[i]);
s[i] = NULL;
}
free(*str);
*str = NULL;
}
}
/**
* Get all filenames in directory
* @param name of directory to search
* @param rows number of files
* @return array of strings (filenames)
*/
char **get_filenames_in_dir(char *dirname, int *rows) {
char buf[4096];
buf[0] = 0;
*rows = 0;
DIR *dir;
struct dirent *ent;
if ((dir = opendir (dirname)) != NULL) {
/* print all the files and directories within directory */
while ((ent = readdir (dir)) != NULL) {
strcat(buf, dirname);
strcat(buf, ent->d_name);
if(!is_regular_file(buf)) {
buf[0] = 0;
continue;
}
buf[0] = 0;
++(*rows);
}
} else {
perror ("");
return NULL;
}
rewinddir(dir);
char **str = malloc(sizeof(char *) * (*rows));
/* print all the files and directories within directory */
int i = 0;
while ((ent = readdir (dir)) != NULL) {
strcat(buf, dirname);
strcat(buf, ent->d_name);
if(!is_regular_file(buf)) {
buf[0] = 0;
continue;
}
str[i] = malloc(sizeof(char) * (strlen(buf) + 1));
strcpy(str[i++], buf);
buf[0] = 0;
}
closedir (dir);
return str;
}
/**
* Tests if path is file or directory
* @param path filename
* @return >= 0 on success
*/
int is_regular_file(const char *path) {
struct stat path_stat;
stat(path, &path_stat);
return S_ISREG(path_stat.st_mode);
}

View File

@@ -41,13 +41,20 @@ typedef struct Clip {
int64_t orig_start_pts, orig_end_pts;
/*
pts of seek, absolute to original video pts
pts of seek, absolute to original video pts.
time_base is the same as VideoContext video_stream time_base
RANGE:
>= orig_start_pts and <= orig_end_pts
>= orig_start_pts and < orig_end_pts
*/
int64_t seek_pts;
/*
By default this is start_frame_pts, but will change when reading packets.
(We track this by video packets seen and seek usage)
*/
int64_t curr_pts;
/*
filename
*/
@@ -56,12 +63,17 @@ typedef struct Clip {
video context open or closed state
*/
bool open;
/*
Current location of the seek pointer within the original file data.
By default this is start_frame_pts, but will change when reading packets.
(We track this by video packets seen and seek usage)
Timebases fetched when the file is first opened.
After the file is open, get timebase from here to avoid opening the file again
*/
int64_t current_frame_idx;
AVRational video_time_base, audio_time_base;
/*
frames per second of original video
*/
double fps;
/********** EDIT SEQUENCE DATA **********/
/*
@@ -88,7 +100,9 @@ typedef struct Clip {
} Clip;
/**
* Allocate clip on heap and initialize to default values
* Allocate clip on heap, initialize default values and open the clip.
* It is important to open the clip when first created so we can set default
* values such as clip->orig_end_pts by reading file contents (length of video)
* @param url filename
* @return NULL on error, not NULL on success
*/

View File

@@ -111,7 +111,16 @@ as a pointer to the first and last element of the list.
**/
void insertSorted(List* list, void* toBeAdded);
/** Uses the comparison function pointer to place the element in the
* appropriate position in the list. Also return the node of inserted element
* should be used as the only insert function if a sorted list is required.
*@pre List exists and has memory allocated to it. Node to be added is valid.
*@post The node to be added will be placed immediately before or after the first occurrence of a related node
*@param list a pointer to the dummy head of the list containing function pointers for delete and compare, as well
as a pointer to the first and last element of the list.
*@param toBeAdded a pointer to data that is to be added to the linked list
**/
Node *insertSortedGetNode(List *list, void *toBeAdded);
/** Removes data from from the list, deletes the node and frees the memory,
* changes pointer values of surrounding nodes to maintain list structure.

137
include/RandomSplice.h Normal file
View File

@@ -0,0 +1,137 @@
#ifndef _RANDOM_SPLICE_
#define _RANDOM_SPLICE_
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <stdlib.h>
#include "OutputContext.h"
#define PICK_FRAMES_RECUR_LIMIT 50
typedef struct RandSpliceParams {
/*************** SEQUENCE PARAMS ***************/
/*
output filename
*/
char output_file[1024];
/*
frames per second to use in sequence
*/
double fps;
/*
audio sample rate
*/
int sample_rate;
/*************** ALGORITHM PARAMS ***************/
/*
directory to fetch video files
*/
char source_dir[1024];
/*
duration of output video (in frames)
*/
int64_t duration;
/*
average length of cuts (in frames)
*/
int cut_len_avg;
/*
variability of average cuts
*/
int cut_len_var;
/*************** INTERNAL ONLY ***************/
int pick_frames_recur;
} RandSpliceParams;
/**
* Make a random edit. This function calls itself recursively to continue making
* cuts until the output sequence is our desired length
* @param os original sequence (input)
* @param ns new sequence (output)
* @param par parameters from user to control algorithm
* @return 0 on success (fully edited sequence), < 0 on fail
*/
int random_edit(Sequence *os, Sequence *ns, RandSpliceParams *par);
/**
* Make a random cut from original sequence and place that clip into new sequence
* @param os original sequence (input)
* @param ns new sequence (output)
* @param par parameters from user to control algorithm
* @return >= 0 on success
*/
int random_cut(Sequence *os, Sequence *ns, RandSpliceParams *par);
/**
* Cut a clip out of original sequence and insert it into new sequence in sorted order
* @param os Original sequence
* @param ns New Sequence
* @param start_index start frame of cut in original sequence
* @param end_index end frame of cut in original sequence
* @return >= 0 on success
*/
int cut_remove_insert(Sequence *os, Sequence *ns, int start_index, int end_index);
/**
* Randomly pick start and end frames given user parameters
* @param seq Original sequence to pick frames
* @param par user parameters
* @param start_index output index of start frame for cut
* @param end_index output index of end frame for cut
* @return >= 0 on success
*/
int pick_frames(Sequence *seq, RandSpliceParams *par, int *start_index, int *end_index);
/**
* Generate random number between range in the uniform distribution
* @param min low bound (inclusive)
* @param max high bound (inclusive)
* @return random number between min and max on a uniform distribution
*/
int rand_range(int min, int max);
/**
* Add all files from string array into sequence!
* @param seq Sequence
* @param files string array of filenames (videos to be added)
* @param num_files number strings in files array
* @return >= 0 on success
*/
int add_files(Sequence *seq, char **files, int num_files);
/**
* print string array
* @param str string array
* @param rows number of strings
*/
void print_str_arr(char **str, int rows);
/**
* Free array of strings
* @param str pointer to array of strings
* @param rows number of strings in array
*/
void free_str_arr(char ***str, int rows);
/**
* Get all filenames in directory
* @param name of directory to search
* @param rows number of files
* @return array of strings (filenames)
*/
char **get_filenames_in_dir(char *dirname, int *rows);
/**
* Tests if path is file or directory
* @param path filename
* @return >= 0 on success
*/
int is_regular_file(const char *path);
#endif

View File

@@ -64,6 +64,16 @@ typedef struct Sequence {
*/
int init_sequence(Sequence *seq, double fps, int sample_rate);
/**
* Initialize a new sequence, list of clips along with a custom compare function (for insertSorted)
* @param seq Sequence to initialize
* @param fps frames per second
* @param sample_rate sample_rate of the audio stream
* @param compareFunc custom compare function used in sorting and searching
* @return >= 0 on success
*/
int init_sequence_cmp(Sequence *seq, double fps, int sample_rate, int (*compareFunc)(const void* first,const void* second));
/**
* Get duration of sequence in frames (defined by fps)
* @param seq Sequence
@@ -103,6 +113,25 @@ void sequence_add_clip_pts(Sequence *seq, Clip *clip, int64_t start_pts);
*/
void sequence_append_clip(Sequence *seq, Clip *clip);
/**
* Insert clip sorted by:
* 1. Date & time of file
* 2. clip->orig_start_pts
* This function will generate the sequence pts for the clip (clip->start_pts and clip->end_pts)
* @param seq Sequence
* @param clip Clip to insert
* @return >= 0 on success
*/
int sequence_insert_clip_sorted(Sequence *seq, Clip *clip);
/**
* Shift clips sequence pts to after the current node (insert clip function)
* @param seq Sequence
* @param curr_node current clip which should shift all following nodes
* @return >= 0 on success
*/
int shift_clips_after(Sequence *seq, Node *curr_node);
/**
* Delete a clip from a sequence and move all following clips forward
* @param seq Sequence

View File

@@ -24,6 +24,12 @@
*/
int sequence_read_frame(Sequence *seq, AVFrame *frame, enum AVMediaType *frame_type, bool close_clips_flag);
/**
* Clear fields on AVFrame from decoding
* @param f AVFrame to initialize
*/
void clear_frame_decoding_garbage(AVFrame *f);
/*************** EXAMPLE FUNCTIONS ***************/
/**
* Test example showing how to read frames from sequence

View File

@@ -13,6 +13,7 @@
#define FFMPEG_SEEK_FLAG AVSEEK_FLAG_BACKWARD
#include "VideoContext.h"
#include <string.h>
/**
* Convert video pts into frame index
@@ -38,4 +39,11 @@ int64_t get_audio_frame_pts(VideoContext *vid_ctx, int frameIndex);
int64_t cov_video_to_audio_pts(VideoContext *vid_ctx, int videoFramePts);
/**
* Print an AVRational struct
* @param tb timebase to print
* @return string allocated on heap. to be freed by caller
*/
char *print_time_base(AVRational *tb);
#endif

View File

@@ -9,13 +9,15 @@
#include "Clip.h"
/**
* Allocate clip on heap and initialize to default values
* Allocate clip on heap, initialize default values and open the clip.
* It is important to open the clip when first created so we can set default
* values such as clip->orig_end_pts by reading file contents (length of video)
* @param url filename
* @return NULL on error, not NULL on success
*/
Clip *alloc_clip(char *url) {
Clip *clip = malloc(sizeof(struct Clip));
if(clip == NULL || init_clip(clip, url) < 0) {
if(clip == NULL || init_clip(clip, url) < 0 || open_clip(clip) < 0) {
return NULL;
}
return clip;
@@ -45,11 +47,12 @@ int init_clip(Clip *clip, char *url) {
clip->orig_end_pts = -1;
clip->seek_pts = 0;
clip->open = false;
clip->current_frame_idx = 0;
clip->start_pts = -1;
clip->end_pts = -1;
clip->done_reading_video = false;
clip->done_reading_audio = false;
clip->fps = 0;
clip->curr_pts = 0;
if(stat(url, &(clip->file_stats)) != 0) {
return -1;
}
@@ -68,15 +71,23 @@ int open_clip(Clip *clip) {
if(!clip->open) {
int ret;
if((ret = open_video(clip->vid_ctx, clip->url)) < 0) {
fprintf(stderr, "Failed to open VideoContext for clip[%s]\n", clip->url);
fprintf(stderr, "open_clip() error: Failed to open VideoContext for clip[%s]\n", clip->url);
return ret;
}
clip->open = true;
AVStream *video_stream = get_video_stream(clip->vid_ctx);
if(clip->orig_end_pts == -1) {
clip->orig_end_pts = get_video_stream(clip->vid_ctx)->duration;
clip->orig_end_pts = video_stream->duration;
}
clip->video_time_base = get_video_time_base(clip->vid_ctx);
clip->audio_time_base = get_audio_time_base(clip->vid_ctx);
int64_t frame_duration = video_stream->duration / video_stream->nb_frames;
clip->fps = clip->video_time_base.den / (double)frame_duration;
init_internal_vars(clip);
seek_clip_pts(clip, 0);
ret = seek_clip_pts(clip, 0);
if(ret < 0) {
return ret;
}
printf("OPEN CLIP [%s]\n", clip->url);
}
return 0;
@@ -155,8 +166,8 @@ int set_clip_start(Clip *clip, int64_t pts) {
int ret = seek_video_pts(clip->vid_ctx, pts);
if(ret >= 0) {
clip->orig_start_pts = pts;
clip->current_frame_idx = 0;
clip->seek_pts = pts;
clip->curr_pts = pts;
}
return ret;
}
@@ -203,20 +214,20 @@ int seek_clip_pts(Clip *clip, int64_t pts) {
int64_t abs_pts = pts + clip->orig_start_pts;
if(pts < 0 || abs_pts > clip->orig_end_pts) {
int64_t endBound = get_clip_end_frame_idx(clip);
fprintf(stderr, "seek pts[%ld] outside of clip bounds (0 - %ld)\n", pts, endBound);
fprintf(stderr, "seek_clip_pts() error: seek pts[%ld] outside of clip bounds (0 - %ld)\n", pts, endBound);
return -1;
}
int ret;
if((ret = seek_video_pts(clip->vid_ctx, abs_pts)) < 0) {
fprintf(stderr, "Failed to seek to pts[%ld] on clip[%s]\n", abs_pts, clip->url);
fprintf(stderr, "seek_clip_pts() error: Failed to seek to pts[%ld] on clip[%s]\n", abs_pts, clip->url);
return ret;
}
clip->seek_pts = abs_pts;
if((ret = cov_video_pts(clip->vid_ctx, abs_pts)) < 0) {
fprintf(stderr, "Failed to convert pts to frame index\n");
fprintf(stderr, "seek_clip_pts error: Failed to convert pts to frame index\n");
return ret;
}
clip->current_frame_idx = ret;
clip->curr_pts = clip->seek_pts;
return 0;
}
@@ -327,11 +338,7 @@ int clip_read_packet(Clip *clip, AVPacket *pkt) {
}
} else {
*pkt = tmpPkt;
if((ret = cov_video_pts(vid_ctx, tmpPkt.pts)) < 0) {
fprintf(stderr, "Failed to convert pts to frame index\n");
return ret;
}
clip->current_frame_idx = ret;
clip->curr_pts = pkt->pts;
}
} else if(tmpPkt.stream_index == vid_ctx->audio_stream_idx) {
if(tmpPkt.pts >= audio_end_pts) {
@@ -410,7 +417,6 @@ int64_t compare_clips_sequential(Clip *f, Clip *s) {
return -2;
}
double diff = difftime(f->file_stats.st_mtime, s->file_stats.st_mtime);
printf("difftime: %f\n", diff);
if(diff < 0.01 && diff > -0.01) {
// if equal date & time
// compare start pts
@@ -539,8 +545,9 @@ int cut_clip_internal(Clip *oc, int64_t pts, Clip **sc) {
AVStream *vid_stream = get_clip_video_stream(oc);
int64_t frame_duration = vid_stream->duration / vid_stream->nb_frames;
if((pts < frame_duration) || (pts >= (oc->orig_end_pts - oc->orig_start_pts))) {
printf("cut_clip_internal(): pts out of range/cannot cut less than one frame\n");
return 1;
printf("cut_clip_internal(): pts out of range/cannot cut less than one frame.. ");
printf("pts: %ld, frame_duration: %ld\n", pts, frame_duration);
return -1;
}
// set orig_end_pts of original clip
int64_t sc_orig_end_pts = oc->orig_end_pts;
@@ -548,7 +555,6 @@ int cut_clip_internal(Clip *oc, int64_t pts, Clip **sc) {
if(ret < 0) {
return ret;
}
*sc = alloc_clip(oc->url);
if(*sc == NULL) {
fprintf(stderr, "cut_clip_internal() error: failed to allocate new clip\n");
@@ -596,7 +602,7 @@ char *list_print_clip(void *toBePrinted) {
} else {
Clip *clip = (Clip *) toBePrinted;
char buf[1024];
sprintf(buf, "ptr: %ld\nstart_frame_pts: %ld\nend_frame_pts: %ld\n",
sprintf(buf, "start_pts: %ld\norig_start_pts: %ld\norig_end_pts: %ld\n",
clip->start_pts, clip->orig_start_pts, clip->orig_end_pts);
char *str = malloc(sizeof(char) * (strlen(buf) + 1));
strcpy(str, buf);

View File

@@ -20,19 +20,66 @@
*/
int clip_read_frame(Clip *clip, AVFrame *frame, enum AVMediaType *frame_type) {
VideoContext *vid_ctx = clip->vid_ctx;
int ret;
// try to receive frame from decoder (from clip_send_packet())
if(vid_ctx->last_decoder_packet_stream == DEC_STREAM_VIDEO) {
*frame_type = AVMEDIA_TYPE_VIDEO;
ret = avcodec_receive_frame(vid_ctx->video_codec_ctx, frame);
return handle_receive_frame(clip, frame, ret, frame_type);
} else if(vid_ctx->last_decoder_packet_stream == DEC_STREAM_AUDIO) {
*frame_type = AVMEDIA_TYPE_AUDIO;
ret = avcodec_receive_frame(vid_ctx->audio_codec_ctx, frame);
return handle_receive_frame(clip, frame, ret, frame_type);
} else {
return clip_send_packet_get_frame(clip, frame, frame_type);
}
int ret, handle_ret = 0;
do {
// try to receive frame from decoder (from clip_send_packet())
if(vid_ctx->last_decoder_packet_stream == DEC_STREAM_VIDEO) {
*frame_type = AVMEDIA_TYPE_VIDEO;
ret = avcodec_receive_frame(vid_ctx->video_codec_ctx, frame);
if(ret == 0) {
// success, frame was returned from decoder
if(frame->pts < clip->seek_pts) {
printf("skip video frame[%ld] before seek[%ld]\n", frame->pts, clip->seek_pts);
handle_ret = 1;
} else {
handle_ret = 0;
}
} else if(ret == AVERROR(EAGAIN)) {
// output is not available in this state - user must try to send new input
ret = clip_send_packet(clip); // if no more packets or error
if(ret < 0) {
return ret;
}
handle_ret = 1;
} else {
// legitimate decoding error
fprintf(stderr, "Error decoding frame (%s)\n", av_err2str(ret));
return -1;
}
} else if(vid_ctx->last_decoder_packet_stream == DEC_STREAM_AUDIO) {
*frame_type = AVMEDIA_TYPE_AUDIO;
ret = avcodec_receive_frame(vid_ctx->audio_codec_ctx, frame);
if(ret == 0) {
// success, frame was returned from decoder
int64_t seek_pts = cov_video_to_audio_pts(clip->vid_ctx, clip->seek_pts);
if(frame->pts < seek_pts) {
printf("skip audio frame[%ld] before seek[%ld]\n", frame->pts, seek_pts);
handle_ret = 1;
} else {
handle_ret = 0;
}
} else if(ret == AVERROR(EAGAIN)) {
// output is not available in this state - user must try to send new input
ret = clip_send_packet(clip); // if no more packets or error
if(ret < 0) {
return ret;
}
handle_ret = 1;
} else {
// legitimate decoding error
fprintf(stderr, "Error decoding frame (%s)\n", av_err2str(ret));
return -1;
}
} else {
ret = clip_send_packet(clip);
// if no more packets or error
if(ret < 0) {
return ret;
}
handle_ret = 1;
}
} while(handle_ret == 1);
return handle_ret;
}
/*************** EXAMPLE FUNCTIONS ***************/
@@ -83,47 +130,37 @@ bool frame_before_seek(Clip *clip, AVFrame *frame, enum AVMediaType type) {
* @param ret return value from calling avcodec_receive_frame()
* @return >= 0 on success
*/
int handle_receive_frame(Clip *clip, AVFrame *frame, int ret, enum AVMediaType *type) {
if(ret == 0) {
// success, frame was returned from decoder
if(frame_before_seek(clip, frame, *type)) {
int64_t seek_pts = clip->seek_pts;
if(*type == AVMEDIA_TYPE_AUDIO) {
seek_pts = cov_video_to_audio_pts(clip->vid_ctx, seek_pts);
printf("skip audio frame[%ld] before seek[%ld]\n", frame->pts, seek_pts);
} else {
printf("skip video frame[%ld] before seek[%ld]\n", frame->pts, seek_pts);
}
// skip frame if its before seek
// (solving the precise seek issue by decoding I-frames and discarding frames before seek time)
return clip_read_frame(clip, frame, type);
}
return 0;
} else if(ret == AVERROR(EAGAIN)) {
// output is not available in this state - user must try to send new input
return clip_send_packet_get_frame(clip, frame, type);
} else {
// legitimate decoding error
fprintf(stderr, "Error decoding frame (%s)\n", av_err2str(ret));
return -1;
}
}
/**
* Sends a single clip packet and get the decoded frame
* @param clip Clip to read packet
* @param frame decoded frame output
* @return >= 0 on success
*/
int clip_send_packet_get_frame(Clip *clip, AVFrame *frame, enum AVMediaType *type) {
int ret = clip_send_packet(clip);
// if no more packets or error
if(ret < 0) {
return ret;
}
return clip_read_frame(clip, frame, type);
}
// int handle_receive_frame(Clip *clip, AVFrame *frame, int ret, enum AVMediaType *type) {
// if(ret == 0) {
// // success, frame was returned from decoder
// if(frame_before_seek(clip, frame, *type)) {
// int64_t seek_pts = clip->seek_pts;
// if(*type == AVMEDIA_TYPE_AUDIO) {
// seek_pts = cov_video_to_audio_pts(clip->vid_ctx, seek_pts);
// printf("skip audio frame[%ld] before seek[%ld]\n", frame->pts, seek_pts);
// } else {
// printf("skip video frame[%ld] before seek[%ld]\n", frame->pts, seek_pts);
// }
//
// // skip frame if its before seek
// // (solving the precise seek issue by decoding I-frames and discarding frames before seek time)
// return 1;
// }
// return 0;
// } else if(ret == AVERROR(EAGAIN)) {
// // output is not available in this state - user must try to send new input
// ret = clip_send_packet(clip);
// // if no more packets or error
// if(ret < 0) {
// return ret;
// }
// return 1;
// } else {
// // legitimate decoding error
// fprintf(stderr, "Error decoding frame (%s)\n", av_err2str(ret));
// return -1;
// }
// }
/**
* Send clip packet to decoder

View File

@@ -176,6 +176,52 @@ void insertSorted(List *list, void *toBeAdded){
return;
}
/** Uses the comparison function pointer to place the element in the
* appropriate position in the list. Also return the node of inserted element
* should be used as the only insert function if a sorted list is required.
*@pre List exists and has memory allocated to it. Node to be added is valid.
*@post The node to be added will be placed immediately before or after the first occurrence of a related node
*@param list a pointer to the dummy head of the list containing function pointers for delete and compare, as well
as a pointer to the first and last element of the list.
*@param toBeAdded a pointer to data that is to be added to the linked list
**/
Node *insertSortedGetNode(List *list, void *toBeAdded){
if (list == NULL || toBeAdded == NULL){
return NULL;
}
if (list->head == NULL){
insertBack(list, toBeAdded);
return list->head;
}
if (list->compare(toBeAdded, list->head->data) <= 0){
insertFront(list, toBeAdded);
return list->head;
}
if (list->compare(toBeAdded, list->tail->data) > 0){
insertBack(list, toBeAdded);
return list->tail;
}
Node* currNode = list->head;
while (currNode != NULL){
if (list->compare(toBeAdded, currNode->data) <= 0){
Node* newNode = initializeNode(toBeAdded);
newNode->next = currNode;
newNode->previous = currNode->previous;
currNode->previous->next = newNode;
currNode->previous = newNode;
(list->length)++;
return newNode;
}
currNode = currNode->next;
}
return NULL;
}
void* deleteDataFromList(List* list, void* toBeDeleted){
if (list == NULL || toBeDeleted == NULL){
return NULL;

View File

@@ -18,11 +18,23 @@
* @return >= 0 on success
*/
int init_sequence(Sequence *seq, double fps, int sample_rate) {
if(seq == NULL) {
fprintf(stderr, "sequence is NULL, cannot initialize");
return init_sequence_cmp(seq, fps, sample_rate, &list_compare_clips);
}
/**
* Initialize a new sequence, list of clips along with a custom compare function (for insertSorted)
* @param seq Sequence to initialize
* @param fps frames per second
* @param sample_rate sample_rate of the audio stream
* @param compareFunc custom compare function used in sorting and searching
* @return >= 0 on success
*/
int init_sequence_cmp(Sequence *seq, double fps, int sample_rate, int (*compareFunc)(const void* first,const void* second)) {
if(seq == NULL || compareFunc == NULL) {
fprintf(stderr, "init_sequence_cmp() error: params cannot be NULL\n");
return -1;
}
seq->clips = initializeList(&list_print_clip, &list_delete_clip, &list_compare_clips);
seq->clips = initializeList(&list_print_clip, &list_delete_clip, compareFunc);
seq->clips_iter = createIterator(seq->clips);
seq->video_time_base = (AVRational){1, fps * SEQ_VIDEO_FRAME_DURATION};
seq->audio_time_base = (AVRational){1, sample_rate};
@@ -115,6 +127,57 @@ void sequence_append_clip(Sequence *seq, Clip *clip) {
}
}
/**
* Insert clip sorted by:
* 1. Date & time of file
* 2. clip->orig_start_pts
* This function will generate the sequence pts for the clip (clip->start_pts and clip->end_pts)
* @param seq Sequence
* @param clip Clip to insert
* @return >= 0 on success
*/
int sequence_insert_clip_sorted(Sequence *seq, Clip *clip) {
if(seq == NULL || clip == NULL) {
fprintf(stderr, "sequence_insert_clip_sorted() error: parameters cannot be NULL\n");
return -1;
}
Node *node = insertSortedGetNode(&(seq->clips), clip);
if(node == NULL) {
fprintf(stderr, "sequence_insert_clip_sorted() error: could not insert clip in sorted order\n");
return -1;
}
seq->clips_iter.current = seq->clips.head;
if(node->previous == NULL) {
move_clip_pts(seq, clip, 0);
} else {
move_clip_pts(seq, clip, ((Clip *) (node->previous->data))->end_pts);
}
return shift_clips_after(seq, node);
}
/**
* Shift clips sequence pts to after the current node (insert clip function)
* @param seq Sequence
* @param curr_node current clip which should shift all following nodes
* @return >= 0 on success
*/
int shift_clips_after(Sequence *seq, Node *curr_node) {
if(seq == NULL || curr_node == NULL) {
fprintf(stderr, "shift_clips_after() error: parameters cannot be NULL\n");
return -1;
}
Clip *curr = (Clip *) (curr_node->data);
int64_t shift = curr->end_pts - curr->start_pts;
Node *node = curr_node->next;
while(node != NULL) {
Clip *next = (Clip *) (node->data);
next->start_pts += shift;
next->end_pts += shift;
node = node->next;
}
return 0;
}
/**
* Delete a clip from a sequence and move all following clips forward
* @param seq Sequence
@@ -222,7 +285,6 @@ int64_t find_clip_at_index(Sequence *seq, int frame_index, Clip **found_clip) {
Node *currNode = seq->clips.head;
while(currNode != NULL) {
Clip *clip = (Clip *) currNode->data;
open_clip(clip);
int64_t clip_pts;
// If clip is found at this frame index (in sequence)
if((clip_pts = seq_frame_within_clip(seq, clip, frame_index)) >= 0) {
@@ -251,9 +313,9 @@ int64_t seq_frame_within_clip(Sequence *seq, Clip *clip, int frame_index) {
int64_t seq_pts = seq_frame_index_to_pts(seq, frame_index);
int64_t pts_diff = seq_pts - clip->start_pts; // find pts relative to the clip!
// if sequence frame is within the clip
if(pts_diff >= 0 && seq_pts <= clip->end_pts) {
if(pts_diff >= 0 && seq_pts < clip->end_pts) {
// convert relative pts to clip time_base
AVRational clip_tb = get_clip_video_time_base(clip);
AVRational clip_tb = clip->video_time_base;
if(clip_tb.num < 0 || clip_tb.den < 0) {
return -1;
}
@@ -273,7 +335,6 @@ int sequence_seek(Sequence *seq, int frame_index) {
Node *currNode = seq->clips.head;
while(currNode != NULL) {
Clip *clip = (Clip *) currNode->data;
open_clip(clip);
int64_t clip_pts;
// If clip is found at this frame index (in sequence)
if((clip_pts = seq_frame_within_clip(seq, clip, frame_index)) >= 0) {

View File

@@ -25,6 +25,7 @@ int sequence_read_frame(Sequence *seq, AVFrame *frame, enum AVMediaType *frame_t
return -1;
}
Clip *curr_clip = (Clip *) currNode->data; // current clip
bool first_frame = (curr_clip->curr_pts == curr_clip->orig_start_pts);
int ret = clip_read_frame(curr_clip, frame, frame_type);
// End of clip!
if(ret < 0) {
@@ -38,7 +39,14 @@ int sequence_read_frame(Sequence *seq, AVFrame *frame, enum AVMediaType *frame_t
if(next == NULL) {
// We're done reading all clips! (reset to start)
printf("We're done reading all clips! (reset to start)\n");
sequence_seek(seq, 0);
if(seq->clips.head != NULL) {
open_clip((Clip *)(seq->clips.head->data));
}
ret = sequence_seek(seq, 0);
if(ret < 0) {
fprintf(stderr, "sequence_read_frame() error: Failed to seek to the start of sequence\n");
return ret;
}
return -1;
} else {
// move onto next clip
@@ -47,8 +55,15 @@ int sequence_read_frame(Sequence *seq, AVFrame *frame, enum AVMediaType *frame_t
return sequence_read_frame(seq, frame, frame_type, close_clips_flag);
}
} else {
clear_frame_decoding_garbage(frame);
// Convert original packet timestamps into sequence timestamps
if(*frame_type == AVMEDIA_TYPE_VIDEO) {
// set first frame to be an I frame
if(first_frame) {
printf("\nI FRAME!\n\n");
frame->key_frame = 1;
frame->pict_type = AV_PICTURE_TYPE_I;
}
frame->pts = video_pkt_to_seq_ts(seq, curr_clip, frame->pts);
// frame->pkt_dts = video_pkt_to_seq_ts(seq, curr_clip, frame->pkt_dts);
} else if(*frame_type == AVMEDIA_TYPE_AUDIO) {
@@ -59,6 +74,27 @@ int sequence_read_frame(Sequence *seq, AVFrame *frame, enum AVMediaType *frame_t
}
}
/**
* Clear fields on AVFrame from decoding
* @param f AVFrame to initialize
*/
void clear_frame_decoding_garbage(AVFrame *f) {
f->key_frame = 0;
f->pict_type = AV_PICTURE_TYPE_NONE;
f->sample_aspect_ratio = (AVRational){0,1};
f->coded_picture_number = 0;
f->display_picture_number = 0;
f->opaque = NULL;
f->repeat_pict = 0;
f->interlaced_frame = 0;
f->side_data = NULL;
f->nb_side_data = 0;
f->flags = 0;
f->decode_error_flags = 0;
f->private_ref = NULL;
f->opaque_ref = NULL;
}
/*************** EXAMPLE FUNCTIONS ***************/
/**
* Test example showing how to read frames from sequence

View File

@@ -145,10 +145,6 @@
return ret;
}
}
// clear_frame_encoding_garbage(oc->buffer_frame);
// oc->buffer_frame->key_frame = 1;
oc->buffer_frame->pict_type = AV_PICTURE_TYPE_NONE;
// supply a raw video or audio frame to the encoder
if(type == AVMEDIA_TYPE_VIDEO) {
ret = avcodec_send_frame(oc->video.codec_ctx, oc->buffer_frame);

View File

@@ -78,6 +78,23 @@ int64_t cov_video_to_audio_pts(VideoContext *vid_ctx, int videoFramePts) {
get_audio_stream(vid_ctx)->time_base);
}
/**
* Print an AVRational struct
* @param tb timebase to print
* @return string allocated on heap. to be freed by caller
*/
char *print_time_base(AVRational *tb) {
char buf[1024];
sprintf(buf, "%d/%d\n", tb->num, tb->den);
char *str = malloc(sizeof(char) * (strlen(buf) + 1));
if(str == NULL) {
fprintf(stderr, "print_time_base(): Failed to allocate memory\n");
return NULL;
}
strcpy(str, buf);
return str;
}
// int seek_audio(VideoContext *vid_ctx, int frame) {
// AVStream *audio_stream = get_audio_stream(vid_ctx);
//