feat: imagorvideo init
This commit is contained in:
commit
2451fa1b5a
20 changed files with 2601 additions and 0 deletions
54
ffmpeg/callback.go
Normal file
54
ffmpeg/callback.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package ffmpeg
|
||||
|
||||
// #include "ffmpeg.h"
|
||||
import "C"
|
||||
import (
|
||||
"github.com/cshum/imagorvideo/ffmpeg/pointer"
|
||||
"io"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//export goPacketRead
|
||||
func goPacketRead(opaque unsafe.Pointer, buf *C.uint8_t, bufSize C.int) C.int {
|
||||
ctx, ok := pointer.Restore(opaque).(*AVContext)
|
||||
if !ok || ctx.reader == nil {
|
||||
return C.int(ErrUnknown)
|
||||
}
|
||||
p := (*[1 << 30]byte)(unsafe.Pointer(buf))[:bufSize:bufSize]
|
||||
n, err := ctx.reader.Read(p)
|
||||
if err == io.EOF {
|
||||
return C.int(ErrEOF)
|
||||
} else if err != nil {
|
||||
return C.int(ErrUnknown)
|
||||
}
|
||||
return C.int(n)
|
||||
}
|
||||
|
||||
//export goPacketSeek
|
||||
func goPacketSeek(opaque unsafe.Pointer, offset C.int64_t, whence C.int) C.int64_t {
|
||||
ctx, ok := pointer.Restore(opaque).(*AVContext)
|
||||
if !ok || ctx.seeker == nil {
|
||||
return C.int64_t(ErrUnknown)
|
||||
}
|
||||
if whence == C.AVSEEK_SIZE && ctx.size > 0 {
|
||||
return C.int64_t(ctx.size)
|
||||
}
|
||||
n, err := ctx.seeker.Seek(int64(offset), int(whence))
|
||||
if err != nil {
|
||||
return C.int64_t(ErrUnknown)
|
||||
}
|
||||
return C.int64_t(n)
|
||||
}
|
||||
|
||||
//export goInterrupt
|
||||
func goInterrupt(opaque unsafe.Pointer) C.int {
|
||||
if ctx, ok := pointer.Restore(opaque).(*AVContext); ok {
|
||||
select {
|
||||
case <-ctx.context.Done():
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
39
ffmpeg/errors.go
Normal file
39
ffmpeg/errors.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package ffmpeg
|
||||
|
||||
// #include "ffmpeg.h"
|
||||
import "C"
|
||||
import (
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type avError int
|
||||
|
||||
const (
|
||||
ErrNoMem = avError(-C.ENOMEM)
|
||||
ErrEOF = avError(C.AVERROR_EOF)
|
||||
ErrUnknown = avError(C.AVERROR_UNKNOWN)
|
||||
ErrDecoderNotFound = avError(C.AVERROR_DECODER_NOT_FOUND)
|
||||
ErrInvalidData = avError(C.AVERROR_INVALIDDATA)
|
||||
ErrTooBig = avError(C.ERR_TOO_BIG)
|
||||
)
|
||||
|
||||
func (e avError) errorString() string {
|
||||
if e == ErrNoMem {
|
||||
return "cannot allocate memory"
|
||||
}
|
||||
if e == ErrTooBig {
|
||||
return "video or cover art size exceeds maximum allowed dimensions"
|
||||
}
|
||||
errString := (*C.char)(C.av_malloc(C.AV_ERROR_MAX_STRING_SIZE))
|
||||
if errString == nil {
|
||||
return "cannot allocate memory for error string, error code: " + strconv.Itoa(int(e))
|
||||
}
|
||||
defer C.av_free(unsafe.Pointer(errString))
|
||||
C.av_make_error_string(errString, C.AV_ERROR_MAX_STRING_SIZE, C.int(e))
|
||||
return C.GoString(errString)
|
||||
}
|
||||
|
||||
func (e avError) Error() string {
|
||||
return "ffmpeg: " + e.errorString()
|
||||
}
|
||||
480
ffmpeg/ffmpeg.c
Normal file
480
ffmpeg/ffmpeg.c
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
#include "ffmpeg.h"
|
||||
|
||||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void free_format_context(AVFormatContext *fmt_ctx) {
|
||||
if (!fmt_ctx) {
|
||||
return;
|
||||
}
|
||||
av_free(fmt_ctx->pb->buffer);
|
||||
avio_context_free(&fmt_ctx->pb);
|
||||
avformat_close_input(&fmt_ctx);
|
||||
}
|
||||
|
||||
int allocate_format_context(AVFormatContext **fmt_ctx) {
|
||||
AVFormatContext *ctx = NULL;
|
||||
if (!(ctx = avformat_alloc_context())) {
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
*fmt_ctx = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_format_context(AVFormatContext *fmt_ctx, void* opaque, int flags) {
|
||||
int err = 0;
|
||||
uint8_t *avio_buffer = NULL;
|
||||
AVIOContext *avio_ctx = NULL;
|
||||
if (!(avio_buffer = av_malloc(BUFFER_SIZE))) {
|
||||
avformat_free_context(fmt_ctx);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
void *reader = NULL;
|
||||
void *seeker = NULL;
|
||||
int write_flag = 0;
|
||||
int seekable = 0;
|
||||
if (flags & READ_PACKET_FLAG) {
|
||||
reader = goPacketRead;
|
||||
}
|
||||
if (flags & SEEK_PACKET_FLAG) {
|
||||
seeker = goPacketSeek;
|
||||
seekable = 1;
|
||||
}
|
||||
if (flags & INTERRUPT_FLAG) {
|
||||
fmt_ctx->interrupt_callback.callback = goInterrupt;
|
||||
fmt_ctx->interrupt_callback.opaque = opaque;
|
||||
}
|
||||
if (!(avio_ctx = avio_alloc_context(avio_buffer, BUFFER_SIZE, write_flag, opaque, reader, NULL, seeker))) {
|
||||
av_free(avio_buffer);
|
||||
avformat_free_context(fmt_ctx);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
fmt_ctx->pb = avio_ctx;
|
||||
fmt_ctx->pb->seekable = seekable;
|
||||
err = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
|
||||
if (err < 0) {
|
||||
av_free(avio_ctx->buffer);
|
||||
avio_context_free(&avio_ctx);
|
||||
free_format_context(fmt_ctx);
|
||||
return err;
|
||||
}
|
||||
err = pthread_mutex_lock(&mutex);
|
||||
if (err < 0) {
|
||||
free_format_context(fmt_ctx);
|
||||
return err;
|
||||
}
|
||||
err = avformat_find_stream_info(fmt_ctx, NULL);
|
||||
int muErr = pthread_mutex_unlock(&mutex);
|
||||
if (err < 0 || muErr < 0) {
|
||||
free_format_context(fmt_ctx);
|
||||
if (muErr < 0) {
|
||||
return muErr;
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int get_orientation(AVStream *video_stream) {
|
||||
uint8_t *display_matrix = av_stream_get_side_data(video_stream, AV_PKT_DATA_DISPLAYMATRIX, NULL);
|
||||
double theta = 0;
|
||||
if (display_matrix) {
|
||||
theta = -av_display_rotation_get((int32_t *) display_matrix);
|
||||
}
|
||||
|
||||
theta -= 360 * floor(theta / 360 + 0.9 / 360);
|
||||
|
||||
int rot = (int) (90 * round(theta / 90)) % 360;
|
||||
|
||||
switch (rot) {
|
||||
case 90:
|
||||
return 6;
|
||||
case 180:
|
||||
return 3;
|
||||
case 270:
|
||||
return 8;
|
||||
default:
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
void get_metadata(AVFormatContext *fmt_ctx, char **artist, char **title) {
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
if ((tag = av_dict_get(fmt_ctx->metadata, "artist", NULL, 0))) {
|
||||
*artist = tag->value;
|
||||
}
|
||||
if ((tag = av_dict_get(fmt_ctx->metadata, "title", NULL, 0))) {
|
||||
*title = tag->value;
|
||||
}
|
||||
}
|
||||
|
||||
int find_streams(AVFormatContext *fmt_ctx, AVStream **video_stream, int *orientation) {
|
||||
int video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
||||
int audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
|
||||
int video_audio = 0;
|
||||
if (audio_stream_index >= 0) {
|
||||
video_audio |= HAS_AUDIO_STREAM;
|
||||
}
|
||||
if (video_stream_index >= 0) {
|
||||
video_audio |= HAS_VIDEO_STREAM;
|
||||
} else {
|
||||
if (video_audio) {
|
||||
return video_audio;
|
||||
}
|
||||
return AVERROR_STREAM_NOT_FOUND;
|
||||
}
|
||||
*video_stream = fmt_ctx->streams[video_stream_index];
|
||||
*orientation = get_orientation(*video_stream);
|
||||
return video_audio;
|
||||
}
|
||||
|
||||
static int open_codec(AVCodecContext *codec_ctx, AVCodec *codec) {
|
||||
int err = pthread_mutex_lock(&mutex);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
err = avcodec_open2(codec_ctx, codec, NULL);
|
||||
int muErr = pthread_mutex_unlock(&mutex);
|
||||
if (muErr < 0) {
|
||||
return muErr;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int create_codec_context(AVStream *video_stream, AVCodecContext **dec_ctx) {
|
||||
AVCodec *dec = NULL;
|
||||
AVCodecParameters *par = video_stream->codecpar;
|
||||
if (par->codec_id == AV_CODEC_ID_VP8) {
|
||||
dec = avcodec_find_decoder_by_name("libvpx");
|
||||
} else if (par->codec_id == AV_CODEC_ID_VP9) {
|
||||
dec = avcodec_find_decoder_by_name("libvpx-vp9");
|
||||
}
|
||||
if (!dec) {
|
||||
dec = avcodec_find_decoder(par->codec_id);
|
||||
}
|
||||
if (dec == NULL) {
|
||||
return AVERROR_DECODER_NOT_FOUND;
|
||||
}
|
||||
if (par->format == -1) {
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
if (av_get_bits_per_pixel(av_pix_fmt_desc_get(par->format)) * par->height * par->width > 1 << 30) {
|
||||
return ERR_TOO_BIG;
|
||||
}
|
||||
if (!(*dec_ctx = avcodec_alloc_context3(dec))) {
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
int err = avcodec_parameters_to_context(*dec_ctx, par);
|
||||
if (err < 0) {
|
||||
avcodec_free_context(dec_ctx);
|
||||
return err;
|
||||
}
|
||||
err = open_codec(*dec_ctx, dec);
|
||||
if (err < 0) {
|
||||
avcodec_free_context(dec_ctx);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
AVFrame *convert_frame_to_rgb(AVFrame *frame, int alpha) {
|
||||
int output_fmt = alpha ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24;
|
||||
struct SwsContext *sws_ctx = NULL;
|
||||
AVFrame *output_frame = av_frame_alloc();
|
||||
if (!output_frame) {
|
||||
return output_frame;
|
||||
}
|
||||
output_frame->height = frame->height;
|
||||
output_frame->width = frame->width;
|
||||
output_frame->format = output_fmt;
|
||||
if (av_frame_get_buffer(output_frame, 1) < 0) {
|
||||
goto free;
|
||||
}
|
||||
if (output_fmt == frame->format) {
|
||||
if (av_frame_copy(output_frame, frame) < 0) {
|
||||
goto free;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
sws_ctx = sws_getContext(frame->width, frame->height, frame->format,
|
||||
output_frame->width, output_frame->height, output_fmt,
|
||||
SWS_LANCZOS | SWS_ACCURATE_RND, NULL, NULL, NULL);
|
||||
if (!sws_ctx) {
|
||||
goto free;
|
||||
}
|
||||
if (sws_scale(sws_ctx, (const uint8_t *const *) frame->data, frame->linesize, 0, frame->height, output_frame->data,
|
||||
output_frame->linesize) != output_frame->height) {
|
||||
goto free;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
free:
|
||||
av_frame_free(&output_frame);
|
||||
done:
|
||||
if (sws_ctx) {
|
||||
sws_freeContext(sws_ctx);
|
||||
}
|
||||
return output_frame;
|
||||
}
|
||||
|
||||
int encode_frame_to_image(AVFormatContext *fmt_ctx, AVFrame *frame, AVPacket *pkt) {
|
||||
AVCodec *enc = avcodec_find_encoder(AV_CODEC_ID_PNG);
|
||||
if (!enc) {
|
||||
return AVERROR_ENCODER_NOT_FOUND;
|
||||
}
|
||||
AVCodecContext *enc_ctx = avcodec_alloc_context3(enc);
|
||||
if (!enc_ctx) {
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
enc_ctx->width = frame->width;
|
||||
enc_ctx->height = frame->height;
|
||||
enc_ctx->pix_fmt = frame->format;
|
||||
enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
enc_ctx->time_base = (AVRational) {1, 1};
|
||||
enc_ctx->compression_level = INT_MAX;
|
||||
int err = open_codec(enc_ctx, enc);
|
||||
if (err < 0) {
|
||||
avcodec_free_context(&enc_ctx);
|
||||
return err;
|
||||
}
|
||||
err = avcodec_send_frame(enc_ctx, frame);
|
||||
if (err < 0) {
|
||||
avcodec_free_context(&enc_ctx);
|
||||
return err;
|
||||
}
|
||||
err = avcodec_receive_packet(enc_ctx, pkt);
|
||||
avcodec_free_context(&enc_ctx);
|
||||
return err;
|
||||
}
|
||||
|
||||
AVPacket create_packet() {
|
||||
AVPacket *pkt = av_packet_alloc();
|
||||
pkt->data = NULL;
|
||||
pkt->size = 0;
|
||||
return *pkt;
|
||||
}
|
||||
|
||||
int
|
||||
obtain_next_frame(AVFormatContext *fmt_ctx, AVCodecContext *dec_ctx, int stream_index, AVPacket *pkt, AVFrame **frame) {
|
||||
int err = 0, retry = 0;
|
||||
if (!(*frame) && !(*frame = av_frame_alloc())) {
|
||||
err = AVERROR(ENOMEM);
|
||||
return err;
|
||||
}
|
||||
if ((err = avcodec_receive_frame(dec_ctx, *frame)) != AVERROR(EAGAIN)) {
|
||||
return err;
|
||||
}
|
||||
while (1) {
|
||||
if ((err = av_read_frame(fmt_ctx, pkt)) < 0) {
|
||||
break;
|
||||
}
|
||||
if (pkt->stream_index != stream_index) {
|
||||
av_packet_unref(pkt);
|
||||
continue;
|
||||
}
|
||||
if ((err = avcodec_send_packet(dec_ctx, pkt)) < 0) {
|
||||
if (retry++ >= 10) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!(*frame) && !(*frame = av_frame_alloc())) {
|
||||
err = AVERROR(ENOMEM);
|
||||
break;
|
||||
}
|
||||
err = avcodec_receive_frame(dec_ctx, *frame);
|
||||
if (err >= 0 || err != AVERROR(EAGAIN)) {
|
||||
break;
|
||||
}
|
||||
av_packet_unref(pkt);
|
||||
}
|
||||
if (pkt->buf) {
|
||||
av_packet_unref(pkt);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int64_t find_duration(AVFormatContext *fmt_ctx) {
|
||||
AVPacket pkt = create_packet();
|
||||
int err = 0;
|
||||
int64_t duration = 0;
|
||||
while (err >= 0) {
|
||||
err = av_read_frame(fmt_ctx, &pkt);
|
||||
if (pkt.pts != AV_NOPTS_VALUE) {
|
||||
AVRational time_base = fmt_ctx->streams[pkt.stream_index]->time_base;
|
||||
duration = FFMAX(duration, pkt.pts * 1000000000 * time_base.num / time_base.den);
|
||||
}
|
||||
av_packet_unref(&pkt);
|
||||
}
|
||||
if (err == AVERROR_EOF) {
|
||||
return duration;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
ThumbContext *create_thumb_context(AVStream *stream, AVFrame *frame) {
|
||||
ThumbContext *thumb_ctx = av_mallocz(sizeof *thumb_ctx);
|
||||
if (!thumb_ctx) {
|
||||
return thumb_ctx;
|
||||
}
|
||||
// thumb_ctx->n = 0;
|
||||
thumb_ctx->desc = av_pix_fmt_desc_get(frame->format);
|
||||
int nb_frames = 100;
|
||||
if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) {
|
||||
nb_frames = 1;
|
||||
} else if (stream->nb_frames && stream->nb_frames < 400) {
|
||||
nb_frames = (int) (stream->nb_frames >> 2) + 1;
|
||||
}
|
||||
int frames_in_128mb = (1 << 30) / (av_get_bits_per_pixel(thumb_ctx->desc) * frame->height * frame->width);
|
||||
thumb_ctx->max_frames = FFMIN(nb_frames, frames_in_128mb);
|
||||
// thumb_ctx->hist_size = 0;
|
||||
// thumb_ctx->alpha = 0;
|
||||
int i;
|
||||
for (i = 0; i < thumb_ctx->desc->nb_components; i++) {
|
||||
thumb_ctx->hist_size += 1 << thumb_ctx->desc->comp[i].depth;
|
||||
}
|
||||
thumb_ctx->median = av_calloc(thumb_ctx->hist_size, sizeof(double));
|
||||
if (!thumb_ctx->median) {
|
||||
av_free(thumb_ctx);
|
||||
return NULL;
|
||||
}
|
||||
thumb_ctx->frames = av_malloc_array((size_t) thumb_ctx->max_frames, sizeof *thumb_ctx->frames);
|
||||
if (!thumb_ctx->frames) {
|
||||
av_free(thumb_ctx->median);
|
||||
av_free(thumb_ctx);
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0; i < thumb_ctx->max_frames; i++) {
|
||||
thumb_ctx->frames[i].frame = NULL;
|
||||
thumb_ctx->frames[i].hist = av_calloc(thumb_ctx->hist_size, sizeof(int));
|
||||
if (!thumb_ctx->frames[i].hist) {
|
||||
for (i--; i >= 0; i--) {
|
||||
av_free(thumb_ctx->frames[i].hist);
|
||||
}
|
||||
av_free(thumb_ctx->median);
|
||||
av_free(thumb_ctx);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return thumb_ctx;
|
||||
}
|
||||
|
||||
void free_thumb_context(ThumbContext *thumb_ctx) {
|
||||
if (!thumb_ctx) {
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < thumb_ctx->n; i++) {
|
||||
av_frame_free(&thumb_ctx->frames[i].frame);
|
||||
av_free(thumb_ctx->frames[i].hist);
|
||||
}
|
||||
for (i = thumb_ctx->n; i < thumb_ctx->max_frames; i++) {
|
||||
av_free(thumb_ctx->frames[i].hist);
|
||||
}
|
||||
av_free(thumb_ctx->median);
|
||||
av_free(thumb_ctx->frames);
|
||||
av_free(thumb_ctx);
|
||||
}
|
||||
|
||||
static double root_mean_square_error(const int *hist, const double *median, size_t hist_size) {
|
||||
int i;
|
||||
double err, sum_sq_err = 0;
|
||||
for (i = 0; i < hist_size; i++) {
|
||||
err = median[i] - (double) hist[i];
|
||||
sum_sq_err += err * err;
|
||||
}
|
||||
return sum_sq_err;
|
||||
}
|
||||
|
||||
static int alpha_check(const AVFrame *frame, const uint64_t flags, const int last_hist_num) {
|
||||
if (flags & AV_PIX_FMT_FLAG_PAL) {
|
||||
for (int i = 3; i <= 1023; i += 4) {
|
||||
if (frame->data[1][i] != 255) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else if (flags & AV_PIX_FMT_FLAG_ALPHA && last_hist_num < frame->width * frame->height) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void populate_histogram(ThumbContext *thumb_ctx, int n, AVFrame *frame) {
|
||||
const AVPixFmtDescriptor *desc = thumb_ctx->desc;
|
||||
thumb_ctx->frames[n].frame = frame;
|
||||
int *hist = thumb_ctx->frames[n].hist;
|
||||
AVComponentDescriptor comp;
|
||||
int w, h, plane, depth, mask, shift, step, height, width;
|
||||
uint64_t flags;
|
||||
uint8_t **data = frame->data;
|
||||
int *linesize = frame->linesize;
|
||||
for (int c = 0; c < desc->nb_components; c++) {
|
||||
comp = desc->comp[c];
|
||||
plane = comp.plane;
|
||||
depth = comp.depth;
|
||||
mask = (1 << depth) - 1;
|
||||
shift = comp.shift;
|
||||
step = comp.step;
|
||||
flags = desc->flags;
|
||||
width = !(desc->log2_chroma_w) || (c != 1 && c != 2) ? frame->width : AV_CEIL_RSHIFT(frame->width,
|
||||
desc->log2_chroma_w);
|
||||
height = !(desc->log2_chroma_h) || (c != 1 && c != 2) ? frame->height : AV_CEIL_RSHIFT(frame->height,
|
||||
desc->log2_chroma_h);
|
||||
for (h = 0; h < height; h++) {
|
||||
w = width;
|
||||
if (flags & AV_PIX_FMT_FLAG_BITSTREAM) {
|
||||
const uint8_t *p = data[plane] + h * linesize[plane] + (comp.offset >> 3);
|
||||
shift = 8 - depth - (comp.offset & 7);
|
||||
|
||||
while (w--) {
|
||||
int val = (*p >> shift) & mask;
|
||||
shift -= step;
|
||||
p -= shift >> 3;
|
||||
shift &= 7;
|
||||
(*(hist + val))++;
|
||||
}
|
||||
} else {
|
||||
const uint8_t *p = data[plane] + h * linesize[plane] + comp.offset;
|
||||
int is_8bit = shift + depth <= 8;
|
||||
|
||||
if (is_8bit)
|
||||
p += (flags & AV_PIX_FMT_FLAG_BE) != 0;
|
||||
|
||||
while (w--) {
|
||||
int val = is_8bit ? *p :
|
||||
flags & AV_PIX_FMT_FLAG_BE ? AV_RB16(p) : AV_RL16(p);
|
||||
val = (val >> shift) & mask;
|
||||
p += step;
|
||||
(*(hist + val))++;
|
||||
}
|
||||
}
|
||||
}
|
||||
hist += 1 << depth;
|
||||
}
|
||||
}
|
||||
|
||||
static AVFrame *get_best_frame(ThumbContext *thumb_ctx) {
|
||||
struct thumb_frame *t_frame = NULL;
|
||||
double min_sum_sq_err = DBL_MAX, sum_sq_err = 0;
|
||||
int i, n = 0;
|
||||
for (i = 0; i < thumb_ctx->n; i++) {
|
||||
t_frame = thumb_ctx->frames + i;
|
||||
sum_sq_err = root_mean_square_error(t_frame->hist, thumb_ctx->median, thumb_ctx->hist_size);
|
||||
if (sum_sq_err < min_sum_sq_err) {
|
||||
min_sum_sq_err = sum_sq_err;
|
||||
n = i;
|
||||
}
|
||||
}
|
||||
thumb_ctx->alpha = alpha_check(thumb_ctx->frames[n].frame, thumb_ctx->desc->flags,
|
||||
thumb_ctx->frames[n].hist[thumb_ctx->hist_size - 1]);
|
||||
return thumb_ctx->frames[n].frame;
|
||||
}
|
||||
|
||||
AVFrame *process_frames(ThumbContext *thumb_ctx) {
|
||||
int i, j, *hist = NULL, n = thumb_ctx->n;
|
||||
double *median = thumb_ctx->median;
|
||||
for (j = 0; j < n; j++) {
|
||||
hist = thumb_ctx->frames[j].hist;
|
||||
for (i = 0; i < thumb_ctx->hist_size; i++) {
|
||||
median[i] += (double) hist[i] / n;
|
||||
}
|
||||
}
|
||||
return get_best_frame(thumb_ctx);
|
||||
}
|
||||
290
ffmpeg/ffmpeg.go
Normal file
290
ffmpeg/ffmpeg.go
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
package ffmpeg
|
||||
|
||||
// #cgo pkg-config: libavformat libavutil libavcodec libswscale
|
||||
// #cgo CFLAGS: -std=c11
|
||||
// #cgo LDFLAGS: -lm
|
||||
// #include "ffmpeg.h"
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"github.com/cshum/imagorvideo/ffmpeg/pointer"
|
||||
"io"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
readPacketFlag = 1
|
||||
seekPacketFlag = 2
|
||||
interruptFlag = 3
|
||||
hasVideo = 1
|
||||
hasAudio = 2
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
Orientation int `json:"orientation"`
|
||||
Duration time.Duration `json:"duration,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Artist string `json:"artist,omitempty"`
|
||||
HasVideo bool `json:"has_video"`
|
||||
HasAudio bool `json:"has_audio"`
|
||||
HasAlpha bool `json:"has_alpha"`
|
||||
}
|
||||
|
||||
type AVContext struct {
|
||||
context context.Context
|
||||
opaque unsafe.Pointer
|
||||
reader io.Reader
|
||||
seeker io.Seeker
|
||||
formatContext *C.AVFormatContext
|
||||
stream *C.AVStream
|
||||
codecContext *C.AVCodecContext
|
||||
thumbContext *C.ThumbContext
|
||||
frame *C.AVFrame
|
||||
durationInFormat bool
|
||||
|
||||
orientation int
|
||||
size int64
|
||||
duration time.Duration
|
||||
width, height int
|
||||
title, artist string
|
||||
hasVideo, hasAudio bool
|
||||
hasFrame, hasAlpha bool
|
||||
}
|
||||
|
||||
func LoadAVContext(ctx context.Context, reader io.Reader, size int64) (*AVContext, error) {
|
||||
av := &AVContext{
|
||||
context: ctx,
|
||||
reader: reader,
|
||||
size: size,
|
||||
}
|
||||
if seeker, ok := reader.(io.Seeker); ok {
|
||||
av.seeker = seeker
|
||||
}
|
||||
flags := C.int(readPacketFlag | interruptFlag)
|
||||
if av.seeker != nil {
|
||||
flags |= seekPacketFlag
|
||||
}
|
||||
err := createFormatContext(av, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !av.hasVideo {
|
||||
return av, nil
|
||||
}
|
||||
if err = createDecoder(av); err == ErrTooBig || err == ErrDecoderNotFound {
|
||||
return av, err
|
||||
}
|
||||
return av, nil
|
||||
}
|
||||
|
||||
func (av *AVContext) ExportImage() ([]byte, error) {
|
||||
if !av.hasFrame {
|
||||
return nil, nil
|
||||
}
|
||||
return encodeFrameImage(av)
|
||||
}
|
||||
|
||||
func (av *AVContext) Close() {
|
||||
freeFormatContext(av)
|
||||
}
|
||||
|
||||
func (av *AVContext) Metadata() *Metadata {
|
||||
return &Metadata{
|
||||
Orientation: av.orientation,
|
||||
Duration: av.duration,
|
||||
Width: av.width,
|
||||
Height: av.height,
|
||||
Title: av.title,
|
||||
Artist: av.artist,
|
||||
HasVideo: av.hasVideo,
|
||||
HasAudio: av.hasAudio,
|
||||
HasAlpha: av.hasAlpha,
|
||||
}
|
||||
}
|
||||
|
||||
func freeFormatContext(av *AVContext) {
|
||||
C.free_format_context(av.formatContext)
|
||||
pointer.Unref(av.opaque)
|
||||
}
|
||||
|
||||
func createFormatContext(av *AVContext, callbackFlags C.int) error {
|
||||
intErr := C.allocate_format_context(&av.formatContext)
|
||||
if intErr < 0 {
|
||||
return avError(intErr)
|
||||
}
|
||||
av.opaque = pointer.Save(av)
|
||||
intErr = C.create_format_context(av.formatContext, av.opaque, callbackFlags)
|
||||
if intErr < 0 {
|
||||
pointer.Unref(av.opaque)
|
||||
return avError(intErr)
|
||||
}
|
||||
metaData(av)
|
||||
duration(av)
|
||||
err := findStreams(av)
|
||||
if err != nil {
|
||||
freeFormatContext(av)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func metaData(av *AVContext) {
|
||||
var artist, title *C.char
|
||||
C.get_metadata(av.formatContext, &artist, &title)
|
||||
av.artist = C.GoString(artist)
|
||||
av.title = C.GoString(title)
|
||||
}
|
||||
|
||||
func duration(av *AVContext) {
|
||||
if av.formatContext.duration > 0 {
|
||||
av.durationInFormat = true
|
||||
av.duration = time.Duration(1000 * av.formatContext.duration)
|
||||
}
|
||||
}
|
||||
|
||||
func fullDuration(av *AVContext) error {
|
||||
if av.durationInFormat {
|
||||
return nil
|
||||
}
|
||||
newDuration := time.Duration(C.find_duration(av.formatContext))
|
||||
if newDuration < 0 {
|
||||
return avError(newDuration)
|
||||
}
|
||||
if newDuration > av.duration {
|
||||
av.duration = newDuration
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findStreams(av *AVContext) error {
|
||||
var orientation C.int
|
||||
err := C.find_streams(av.formatContext, &av.stream, &orientation)
|
||||
if err < 0 {
|
||||
return avError(err)
|
||||
}
|
||||
av.hasVideo = err&hasVideo != 0
|
||||
av.hasAudio = err&hasAudio != 0
|
||||
if av.hasVideo {
|
||||
av.width = int(av.stream.codecpar.width)
|
||||
av.height = int(av.stream.codecpar.height)
|
||||
av.orientation = int(orientation)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createDecoder(av *AVContext) error {
|
||||
err := C.create_codec_context(av.stream, &av.codecContext)
|
||||
if err < 0 {
|
||||
return avError(err)
|
||||
}
|
||||
defer C.avcodec_free_context(&av.codecContext)
|
||||
return createThumbContext(av)
|
||||
}
|
||||
|
||||
func incrementDuration(av *AVContext, frame *C.AVFrame) {
|
||||
if !av.durationInFormat && frame.pts != C.AV_NOPTS_VALUE {
|
||||
ptsToNano := C.int64_t(1000000000 * av.stream.time_base.num / av.stream.time_base.den)
|
||||
newDuration := time.Duration(frame.pts * ptsToNano)
|
||||
if newDuration > av.duration {
|
||||
av.duration = newDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func populateHistogram(av *AVContext, frames <-chan *C.AVFrame) <-chan struct{} {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
var n C.int
|
||||
for frame := range frames {
|
||||
C.populate_histogram(av.thumbContext, n, frame)
|
||||
n++
|
||||
}
|
||||
av.thumbContext.n = n
|
||||
done <- struct{}{}
|
||||
close(done)
|
||||
}()
|
||||
return done
|
||||
}
|
||||
|
||||
func createThumbContext(av *AVContext) error {
|
||||
pkt := C.create_packet()
|
||||
var frame *C.AVFrame
|
||||
err := C.obtain_next_frame(av.formatContext, av.codecContext, av.stream.index, &pkt, &frame)
|
||||
if err >= 0 {
|
||||
incrementDuration(av, frame)
|
||||
av.thumbContext = C.create_thumb_context(av.stream, frame)
|
||||
if av.thumbContext == nil {
|
||||
err = C.int(ErrNoMem)
|
||||
}
|
||||
}
|
||||
if err < 0 {
|
||||
if pkt.buf != nil {
|
||||
C.av_packet_unref(&pkt)
|
||||
}
|
||||
if frame != nil {
|
||||
C.av_frame_free(&frame)
|
||||
}
|
||||
return avError(err)
|
||||
}
|
||||
defer C.free_thumb_context(av.thumbContext)
|
||||
frames := make(chan *C.AVFrame, av.thumbContext.max_frames)
|
||||
done := populateHistogram(av, frames)
|
||||
frames <- frame
|
||||
if pkt.buf != nil {
|
||||
C.av_packet_unref(&pkt)
|
||||
}
|
||||
return populateThumbContext(av, frames, done)
|
||||
}
|
||||
|
||||
func populateThumbContext(av *AVContext, frames chan *C.AVFrame, done <-chan struct{}) error {
|
||||
pkt := C.create_packet()
|
||||
var frame *C.AVFrame
|
||||
var err C.int
|
||||
for i := C.int(1); i < av.thumbContext.max_frames; i++ {
|
||||
err = C.obtain_next_frame(av.formatContext, av.codecContext, av.stream.index, &pkt, &frame)
|
||||
if err < 0 {
|
||||
break
|
||||
}
|
||||
incrementDuration(av, frame)
|
||||
frames <- frame
|
||||
frame = nil
|
||||
}
|
||||
close(frames)
|
||||
if pkt.buf != nil {
|
||||
C.av_packet_unref(&pkt)
|
||||
}
|
||||
if frame != nil {
|
||||
C.av_frame_free(&frame)
|
||||
}
|
||||
<-done
|
||||
if err != 0 && err != C.int(ErrEOF) {
|
||||
return avError(err)
|
||||
}
|
||||
return convertFrameToRGB(av)
|
||||
}
|
||||
|
||||
func convertFrameToRGB(av *AVContext) error {
|
||||
outputFrame := C.convert_frame_to_rgb(C.process_frames(av.thumbContext), av.thumbContext.alpha)
|
||||
if outputFrame == nil {
|
||||
return ErrNoMem
|
||||
}
|
||||
av.frame = outputFrame
|
||||
av.hasFrame = true
|
||||
av.hasAlpha = av.thumbContext.alpha != 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeFrameImage(av *AVContext) ([]byte, error) {
|
||||
pkt := C.create_packet()
|
||||
err := C.encode_frame_to_image(av.formatContext, av.frame, &pkt)
|
||||
if err < 0 {
|
||||
return nil, avError(err)
|
||||
}
|
||||
p := (*[1 << 30]byte)(unsafe.Pointer(pkt.data))[:pkt.size:pkt.size]
|
||||
if pkt.buf != nil {
|
||||
C.av_packet_unref(&pkt)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
72
ffmpeg/ffmpeg.h
Normal file
72
ffmpeg/ffmpeg.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <float.h>
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/file.h>
|
||||
#include <libavutil/pixfmt.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libavutil/intreadwrite.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/display.h>
|
||||
|
||||
#define BUFFER_SIZE 1 << 12
|
||||
#define READ_PACKET_FLAG 1
|
||||
#define SEEK_PACKET_FLAG 2
|
||||
#define INTERRUPT_FLAG 3
|
||||
#define HAS_VIDEO_STREAM 1
|
||||
#define HAS_AUDIO_STREAM 2
|
||||
#define ERR_TOO_BIG FFERRTAG('H','M','M','M')
|
||||
|
||||
struct thumb_frame {
|
||||
AVFrame *frame;
|
||||
int *hist;
|
||||
};
|
||||
|
||||
typedef struct ThumbContext {
|
||||
int n, alpha, max_frames;
|
||||
struct thumb_frame *frames;
|
||||
double *median;
|
||||
const AVPixFmtDescriptor *desc;
|
||||
size_t hist_size;
|
||||
} ThumbContext;
|
||||
|
||||
int allocate_format_context(AVFormatContext **fmt_ctx);
|
||||
|
||||
int create_format_context(AVFormatContext *fmt_ctx, void* opaque, int callbacks);
|
||||
|
||||
void free_format_context(AVFormatContext *fmt_ctx);
|
||||
|
||||
void get_metadata(AVFormatContext *fmt_ctx, char **artist, char **title);
|
||||
|
||||
int find_streams(AVFormatContext *fmt_ctx, AVStream **video_stream, int *orientation);
|
||||
|
||||
int create_codec_context(AVStream *video_stream, AVCodecContext **dec_ctx);
|
||||
|
||||
AVFrame *convert_frame_to_rgb(AVFrame *frame, int alpha);
|
||||
|
||||
int encode_frame_to_image(AVFormatContext *fmt_ctx, AVFrame *frame, AVPacket *pkt);
|
||||
|
||||
AVPacket create_packet();
|
||||
|
||||
int
|
||||
obtain_next_frame(AVFormatContext *fmt_ctx, AVCodecContext *dec_ctx, int stream_index, AVPacket *pkt, AVFrame **frame);
|
||||
|
||||
int64_t find_duration(AVFormatContext *fmt_ctx);
|
||||
|
||||
ThumbContext *create_thumb_context(AVStream *stream, AVFrame *frame);
|
||||
|
||||
void free_thumb_context(ThumbContext *thumb_ctx);
|
||||
|
||||
AVFrame *process_frames(ThumbContext *thumb_ctx);
|
||||
|
||||
void populate_histogram(ThumbContext *thumb_ctx, int n, AVFrame *frame);
|
||||
|
||||
extern int goPacketRead(void *opaque, uint8_t *buf, int buf_size);
|
||||
|
||||
extern int64_t goPacketSeek(void *opaque, int64_t seek, int whence);
|
||||
|
||||
extern int goInterrupt(void *opaque);
|
||||
29
ffmpeg/logging.go
Normal file
29
ffmpeg/logging.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package ffmpeg
|
||||
|
||||
// #include "ffmpeg.h"
|
||||
import "C"
|
||||
|
||||
// AVLogLevel defines the ffmpeg threshold for dumping information to stderr.
|
||||
type AVLogLevel int
|
||||
|
||||
// Possible values for AVLogLevel.
|
||||
const (
|
||||
AVLogQuiet AVLogLevel = (iota - 1) * 8
|
||||
AVLogPanic
|
||||
AVLogFatal
|
||||
AVLogError
|
||||
AVLogWarning
|
||||
AVLogInfo
|
||||
AVLogVerbose
|
||||
AVLogDebug
|
||||
AVLogTrace
|
||||
)
|
||||
|
||||
func logLevel() AVLogLevel {
|
||||
return AVLogLevel(C.av_log_get_level())
|
||||
}
|
||||
|
||||
// SetFFmpegLogLevel allows you to change the log level from the default (AVLogInfo).
|
||||
func SetFFmpegLogLevel(logLevel AVLogLevel) {
|
||||
C.av_log_set_level(C.int(logLevel))
|
||||
}
|
||||
100
ffmpeg/pointer/pointer.go
Normal file
100
ffmpeg/pointer/pointer.go
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package pointer
|
||||
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const blockSize = 1024
|
||||
|
||||
var (
|
||||
mutex sync.RWMutex
|
||||
store = map[unsafe.Pointer]interface{}{}
|
||||
free []unsafe.Pointer
|
||||
blocks []unsafe.Pointer
|
||||
)
|
||||
|
||||
func allocMem() {
|
||||
mem := C.malloc(blockSize)
|
||||
if mem == nil {
|
||||
panic("can't allocate memory block for C pointers")
|
||||
}
|
||||
blocks = append(blocks, mem)
|
||||
for i := 0; i < blockSize; i++ {
|
||||
p := unsafe.Pointer(uintptr(mem) + uintptr(blockSize-1-i))
|
||||
free = append(free, p)
|
||||
}
|
||||
}
|
||||
|
||||
func getPtr() unsafe.Pointer {
|
||||
// Generate real fake C pointer.
|
||||
// This pointer will not store any data, but will be used for indexing
|
||||
// purposes. Since Go doesn't allow to cast dangling pointer to
|
||||
// unsafe.Pointer, we do really allocate memory. Why we need indexing? Because
|
||||
// Go doest allow C code to store pointers to Go data.
|
||||
if len(free) == 0 {
|
||||
allocMem()
|
||||
}
|
||||
n := len(free) - 1
|
||||
p := free[n]
|
||||
free = free[:n]
|
||||
return p
|
||||
}
|
||||
|
||||
// Save an object in the storage and return an index pointer to it.
|
||||
func Save(v interface{}) unsafe.Pointer {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
ptr := getPtr()
|
||||
store[ptr] = v
|
||||
mutex.Unlock()
|
||||
|
||||
return ptr
|
||||
}
|
||||
|
||||
// Restore an object from the storage by its index pointer.
|
||||
func Restore(ptr unsafe.Pointer) (v interface{}) {
|
||||
if ptr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mutex.RLock()
|
||||
v = store[ptr]
|
||||
mutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Unref removes an object from the storage and returns the index pointer to the
|
||||
// pool for reuse.
|
||||
func Unref(ptr unsafe.Pointer) {
|
||||
if ptr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
if _, ok := store[ptr]; ok {
|
||||
delete(store, ptr)
|
||||
free = append(free, ptr)
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// Clear storage and free all memory
|
||||
func Clear() {
|
||||
mutex.Lock()
|
||||
for p := range store {
|
||||
delete(store, p)
|
||||
}
|
||||
free = nil
|
||||
for _, p := range blocks {
|
||||
C.free(p)
|
||||
}
|
||||
blocks = nil
|
||||
mutex.Unlock()
|
||||
}
|
||||
176
ffmpeg/pointer/pointer_test.go
Normal file
176
ffmpeg/pointer/pointer_test.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package pointer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPointer(t *testing.T) {
|
||||
t.Cleanup(Clear)
|
||||
// assert := makeAssert(t)
|
||||
assert.Len(t, store, 0)
|
||||
assert.Len(t, free, 0)
|
||||
assert.Len(t, blocks, 0)
|
||||
mutex.Lock()
|
||||
assert.Equal(t, unsafe.Pointer(nil), Save(nil))
|
||||
assert.Nil(t, Restore(nil))
|
||||
Unref(nil)
|
||||
mutex.Unlock()
|
||||
i1 := Save("foo")
|
||||
i2 := Save("bar")
|
||||
i3 := Save("baz")
|
||||
assert.Len(t, store, 3)
|
||||
assert.Len(t, free, blockSize-3)
|
||||
assert.Len(t, blocks, 1)
|
||||
var x interface{}
|
||||
x = Restore(i1)
|
||||
assert.NotNil(t, x)
|
||||
if s, ok := x.(string); ok {
|
||||
assert.Equal(t, "foo", s)
|
||||
} else {
|
||||
t.Fail()
|
||||
}
|
||||
x = Restore(unsafe.Pointer(&x))
|
||||
assert.Nil(t, x)
|
||||
x = Restore(i3)
|
||||
assert.NotNil(t, x)
|
||||
if s, ok := x.(string); ok {
|
||||
assert.Equal(t, "baz", s)
|
||||
} else {
|
||||
t.Fail()
|
||||
}
|
||||
Unref(i3)
|
||||
x = Restore(i3)
|
||||
assert.Nil(t, x)
|
||||
Unref(i2)
|
||||
Unref(i1)
|
||||
assert.Len(t, store, 0)
|
||||
assert.Len(t, free, blockSize)
|
||||
assert.Len(t, blocks, 1)
|
||||
i3 = Save("baz")
|
||||
assert.Len(t, store, 1)
|
||||
assert.Len(t, free, blockSize-1)
|
||||
Clear()
|
||||
assert.Len(t, store, 0)
|
||||
assert.Len(t, free, 0)
|
||||
assert.Len(t, blocks, 0)
|
||||
}
|
||||
|
||||
func TestPointerIndexing(t *testing.T) {
|
||||
t.Cleanup(Clear)
|
||||
assert.Len(t, store, 0)
|
||||
assert.Len(t, free, 0)
|
||||
assert.Len(t, blocks, 0)
|
||||
|
||||
i1 := Save("foo")
|
||||
i2 := Save("bar")
|
||||
_ = Save("baz")
|
||||
_ = Save("wibble")
|
||||
_ = Save("wabble")
|
||||
assert.Len(t, store, 5)
|
||||
assert.Len(t, free, blockSize-5)
|
||||
|
||||
// Check that when we remove the first items inserted into the map there are
|
||||
// no subsequent issues
|
||||
Unref(i1)
|
||||
Unref(i2)
|
||||
assert.Len(t, free, blockSize-3)
|
||||
_ = Save("flim")
|
||||
ilast := Save("flam")
|
||||
assert.Len(t, store, 5)
|
||||
assert.Len(t, free, blockSize-5)
|
||||
|
||||
x := Restore(ilast)
|
||||
assert.NotNil(t, x)
|
||||
if s, ok := x.(string); ok {
|
||||
assert.Equal(t, "flam", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallbacksData(t *testing.T) {
|
||||
t.Cleanup(Clear)
|
||||
assert.Len(t, store, 0)
|
||||
assert.Len(t, free, 0)
|
||||
assert.Len(t, blocks, 0)
|
||||
|
||||
// insert a plain function
|
||||
i1 := Save(func(v int) int { return v + 1 })
|
||||
|
||||
// insert a type "containing" a function, note that it doesn't
|
||||
// actually have a callable function. Users of the type must
|
||||
// check that themselves
|
||||
type flup struct {
|
||||
Stuff int
|
||||
Junk func(int, int) error
|
||||
}
|
||||
i2 := Save(flup{
|
||||
Stuff: 55,
|
||||
})
|
||||
|
||||
// did we get a function back
|
||||
x1 := Restore(i1)
|
||||
if assert.NotNil(t, x1) {
|
||||
if f, ok := x1.(func(v int) int); ok {
|
||||
assert.Equal(t, 2, f(1))
|
||||
} else {
|
||||
t.Fatalf("conversion failed")
|
||||
}
|
||||
}
|
||||
|
||||
// did we get our data structure back
|
||||
x2 := Restore(i2)
|
||||
if assert.NotNil(t, x2) {
|
||||
if d, ok := x2.(flup); ok {
|
||||
assert.Equal(t, 55, d.Stuff)
|
||||
assert.Nil(t, d.Junk)
|
||||
} else {
|
||||
t.Fatalf("conversion failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPointer(t *testing.B) {
|
||||
t.Cleanup(Clear)
|
||||
assert.Len(t, store, 0)
|
||||
assert.Len(t, free, 0)
|
||||
assert.Len(t, blocks, 0)
|
||||
const workers = 1000
|
||||
var wg sync.WaitGroup
|
||||
f := func() {
|
||||
defer wg.Done()
|
||||
var x interface{}
|
||||
var i1, i2, i3 unsafe.Pointer
|
||||
for i := 0; i < t.N/workers; i++ {
|
||||
i1 = Save("foo")
|
||||
i2 = Save("bar")
|
||||
i3 = Save("baz")
|
||||
x = Restore(i1)
|
||||
assert.NotNil(t, x)
|
||||
if s, ok := x.(string); ok {
|
||||
assert.Equal(t, "foo", s)
|
||||
} else {
|
||||
t.Fail()
|
||||
}
|
||||
x = Restore(i3)
|
||||
assert.NotNil(t, x)
|
||||
if s, ok := x.(string); ok {
|
||||
assert.Equal(t, "baz", s)
|
||||
} else {
|
||||
t.Fail()
|
||||
}
|
||||
Unref(i3)
|
||||
Unref(i2)
|
||||
Unref(i1)
|
||||
}
|
||||
}
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go f()
|
||||
}
|
||||
wg.Wait()
|
||||
assert.Len(t, store, 0)
|
||||
assert.Len(t, free, len(blocks)*blockSize)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue