You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

833 lines
22 KiB

/*
libde265 example application "dec265".
MIT License
Copyright (c) 2013-2014 struktur AG, Dirk Farin <farin@struktur.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#define DO_MEMORY_LOGGING 0
#include "de265.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <limits>
#include <getopt.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <signal.h>
#ifndef _MSC_VER
#include <sys/time.h>
#include <unistd.h>
#endif
#include "libde265/quality.h"
#if HAVE_VIDEOGFX
#include <libvideogfx.hh>
using namespace videogfx;
#endif
#if HAVE_SDL
#include "sdl.hh"
#endif
#define BUFFER_SIZE 40960
#define NUM_THREADS 4
int nThreads=0;
bool nal_input=false;
int quiet=0;
bool check_hash=false;
bool show_help=false;
bool dump_headers=false;
bool write_yuv=false;
bool output_with_videogfx=false;
bool logging=true;
bool no_acceleration=false;
const char *output_filename = "out.yuv";
uint32_t max_frames=UINT32_MAX;
bool write_bytestream=false;
const char *bytestream_filename;
bool measure_quality=false;
bool show_ssim_map=false;
bool show_psnr_map=false;
const char* reference_filename;
FILE* reference_file;
int highestTID = 100;
int verbosity=0;
int disable_deblocking=0;
int disable_sao=0;
static struct option long_options[] = {
{"quiet", no_argument, 0, 'q' },
{"threads", required_argument, 0, 't' },
{"check-hash", no_argument, 0, 'c' },
{"profile", no_argument, 0, 'p' },
{"frames", required_argument, 0, 'f' },
{"output", required_argument, 0, 'o' },
{"dump", no_argument, 0, 'd' },
{"nal", no_argument, 0, 'n' },
{"videogfx", no_argument, 0, 'V' },
{"no-logging", no_argument, 0, 'L' },
{"help", no_argument, 0, 'h' },
{"noaccel", no_argument, 0, '0' },
{"write-bytestream", required_argument,0, 'B' },
{"measure", required_argument, 0, 'm' },
{"ssim", no_argument, 0, 's' },
{"errmap", no_argument, 0, 'e' },
{"highest-TID", required_argument, 0, 'T' },
{"verbose", no_argument, 0, 'v' },
{"disable-deblocking", no_argument, &disable_deblocking, 1 },
{"disable-sao", no_argument, &disable_sao, 1 },
{0, 0, 0, 0 }
};
static void write_picture(const de265_image* img)
{
static FILE* fh = NULL;
if (fh==NULL) {
if (strcmp(output_filename, "-") == 0) {
fh = stdout;
} else {
fh = fopen(output_filename, "wb");
}
}
for (int c=0;c<3;c++) {
int stride;
const uint8_t* p = de265_get_image_plane(img, c, &stride);
int width = de265_get_image_width(img,c);
if (de265_get_bits_per_pixel(img,c)<=8) {
// --- save 8 bit YUV ---
for (int y=0;y<de265_get_image_height(img,c);y++) {
fwrite(p + y*stride, width, 1, fh);
}
}
else {
// --- save 16 bit YUV ---
int bpp = (de265_get_bits_per_pixel(img,c)+7)/8;
int pixelsPerLine = stride/bpp;
uint8_t* buf = new uint8_t[width*2];
uint16_t* p16 = (uint16_t*)p;
for (int y=0;y<de265_get_image_height(img,c);y++) {
for (int x=0;x<width;x++) {
uint16_t pixel_value = (p16+y*pixelsPerLine)[x];
buf[2*x+0] = pixel_value & 0xFF;
buf[2*x+1] = pixel_value >> 8;
}
fwrite(buf, width*2, 1, fh);
}
delete[] buf;
}
}
fflush(fh);
}
#if HAVE_VIDEOGFX
void display_image(const struct de265_image* img)
{
static X11Win win;
// display picture
static bool first=true;
if (first) {
first=false;
win.Create(de265_get_image_width(img,0),
de265_get_image_height(img,0),
"de265 output");
}
int width = de265_get_image_width(img,0);
int height = de265_get_image_height(img,0);
de265_chroma chroma = de265_get_chroma_format(img);
ChromaFormat vgfx_chroma;
Colorspace vgfx_cs = Colorspace_YUV;
switch (chroma) {
case de265_chroma_420: vgfx_chroma = Chroma_420; break;
case de265_chroma_422: vgfx_chroma = Chroma_422; break;
case de265_chroma_444: vgfx_chroma = Chroma_444; break;
case de265_chroma_mono: vgfx_cs = Colorspace_Greyscale; break;
}
Image<Pixel> visu;
visu.Create(width, height, vgfx_cs, vgfx_chroma);
int nChannels = 3;
if (chroma == de265_chroma_mono) {
nChannels = 1;
}
for (int ch=0;ch<nChannels;ch++) {
const uint8_t* data;
int stride;
data = de265_get_image_plane(img,ch,&stride);
width = de265_get_image_width(img,ch);
height = de265_get_image_height(img,ch);
int bit_depth = de265_get_bits_per_pixel(img,ch);
if (bit_depth==8) {
for (int y=0;y<height;y++) {
memcpy(visu.AskFrame((BitmapChannel)ch)[y], data + y*stride, width);
}
}
else {
const uint16_t* data16 = (const uint16_t*)data;
for (int y=0;y<height;y++) {
for (int x=0;x<width;x++) {
visu.AskFrame((BitmapChannel)ch)[y][x] = *(data16 + y*stride +x) >> (bit_depth-8);
}
}
}
}
win.Display(visu);
win.WaitForKeypress();
}
#endif
static uint8_t* convert_to_8bit(const uint8_t* data, int width, int height,
int pixelsPerLine, int bit_depth)
{
const uint16_t* data16 = (const uint16_t*)data;
uint8_t* out = new uint8_t[pixelsPerLine*height];
for (int y=0;y<height;y++) {
for (int x=0;x<width;x++) {
out[y*pixelsPerLine + x] = *(data16 + y*pixelsPerLine +x) >> (bit_depth-8);
}
}
return out;
}
#if HAVE_SDL
SDL_YUV_Display sdlWin;
bool sdl_active=false;
bool display_sdl(const struct de265_image* img)
{
int width = de265_get_image_width(img,0);
int height = de265_get_image_height(img,0);
int chroma_width = de265_get_image_width(img,1);
int chroma_height = de265_get_image_height(img,1);
de265_chroma chroma = de265_get_chroma_format(img);
if (!sdl_active) {
sdl_active=true;
enum SDL_YUV_Display::SDL_Chroma sdlChroma;
switch (chroma) {
case de265_chroma_420: sdlChroma = SDL_YUV_Display::SDL_CHROMA_420; break;
case de265_chroma_422: sdlChroma = SDL_YUV_Display::SDL_CHROMA_422; break;
case de265_chroma_444: sdlChroma = SDL_YUV_Display::SDL_CHROMA_444; break;
case de265_chroma_mono: sdlChroma = SDL_YUV_Display::SDL_CHROMA_MONO; break;
}
sdlWin.init(width,height, sdlChroma);
}
int stride,chroma_stride;
const uint8_t* y = de265_get_image_plane(img,0,&stride);
const uint8_t* cb =de265_get_image_plane(img,1,&chroma_stride);
const uint8_t* cr =de265_get_image_plane(img,2,NULL);
int bpp_y = (de265_get_bits_per_pixel(img,0)+7)/8;
int bpp_c = (de265_get_bits_per_pixel(img,1)+7)/8;
int ppl_y = stride/bpp_y;
int ppl_c = chroma_stride/bpp_c;
uint8_t* y16 = NULL;
uint8_t* cb16 = NULL;
uint8_t* cr16 = NULL;
int bd;
if ((bd=de265_get_bits_per_pixel(img, 0)) > 8) {
y16 = convert_to_8bit(y, width,height,ppl_y,bd); y=y16;
}
if (chroma != de265_chroma_mono) {
if ((bd=de265_get_bits_per_pixel(img, 1)) > 8) {
cb16 = convert_to_8bit(cb, chroma_width,chroma_height,ppl_c,bd); cb=cb16;
}
if ((bd=de265_get_bits_per_pixel(img, 2)) > 8) {
cr16 = convert_to_8bit(cr, chroma_width,chroma_height,ppl_c,bd); cr=cr16;
}
}
sdlWin.display(y,cb,cr, ppl_y, ppl_c);
delete[] y16;
delete[] cb16;
delete[] cr16;
return sdlWin.doQuit();
}
#endif
static int width,height;
static uint32_t framecnt=0;
bool output_image(const de265_image* img)
{
bool stop=false;
width = de265_get_image_width(img,0);
height = de265_get_image_height(img,0);
framecnt++;
//printf("SHOW POC: %d / PTS: %ld / integrity: %d\n",img->PicOrderCntVal, img->pts, img->integrity);
if (0) {
const char* nal_unit_name;
int nuh_layer_id;
int nuh_temporal_id;
de265_get_image_NAL_header(img, NULL, &nal_unit_name, &nuh_layer_id, &nuh_temporal_id);
printf("NAL: %s layer:%d temporal:%d\n",nal_unit_name, nuh_layer_id, nuh_temporal_id);
}
if (!quiet) {
#if HAVE_SDL && HAVE_VIDEOGFX
if (output_with_videogfx) {
display_image(img);
} else {
stop = display_sdl(img);
}
#elif HAVE_SDL
stop = display_sdl(img);
#elif HAVE_VIDEOGFX
display_image(img);
#endif
}
if (write_yuv) {
write_picture(img);
}
if ((framecnt%100)==0) {
fprintf(stderr,"frame %d\r",framecnt);
}
if (framecnt>=max_frames) {
stop=true;
}
return stop;
}
static double mse_y=0.0, mse_cb=0.0, mse_cr=0.0;
static int mse_frames=0;
static double ssim_y=0.0;
static int ssim_frames=0;
void measure(const de265_image* img)
{
// --- compute PSNR ---
int width = de265_get_image_width(img,0);
int height = de265_get_image_height(img,0);
uint8_t* p = (uint8_t*)malloc(width*height*3/2);
if (p == NULL) {
return;
}
size_t toread = width*height*3/2;
if (fread(p,1,toread,reference_file) != toread) {
free(p);
return;
}
int stride, cstride;
const uint8_t* yptr = de265_get_image_plane(img,0, &stride);
const uint8_t* cbptr = de265_get_image_plane(img,1, &cstride);
const uint8_t* crptr = de265_get_image_plane(img,2, &cstride);
double img_mse_y = MSE( yptr, stride, p, width, width, height);
double img_mse_cb = MSE(cbptr, cstride, p+width*height, width/2, width/2,height/2);
double img_mse_cr = MSE(crptr, cstride, p+width*height*5/4, width/2, width/2,height/2);
mse_frames++;
mse_y += img_mse_y;
mse_cb += img_mse_cb;
mse_cr += img_mse_cr;
// --- compute SSIM ---
double ssimSum = 0.0;
#if HAVE_VIDEOGFX
Bitmap<Pixel> ref, coded;
ref .Create(width, height); // reference image
coded.Create(width, height); // coded image
const uint8_t* data;
data = de265_get_image_plane(img,0,&stride);
for (int y=0;y<height;y++) {
memcpy(coded[y], data + y*stride, width);
memcpy(ref[y], p + y*stride, width);
}
SSIM ssimAlgo;
Bitmap<float> ssim = ssimAlgo.calcSSIM(ref,coded);
Bitmap<Pixel> ssimMap;
ssimMap.Create(width,height);
for (int y=0;y<height;y++)
for (int x=0;x<width;x++)
{
float v = ssim[y][x];
ssimSum += v;
v = v*v;
v = 255*v; //pow(v, 20);
//assert(v<=255.0);
ssimMap[y][x] = v;
}
ssimSum /= width*height;
Bitmap<Pixel> error_map = CalcErrorMap(ref, coded, TransferCurve_Sqrt);
// display PSNR error map
if (show_psnr_map) {
static X11Win win;
static bool first=true;
if (first) {
first=false;
win.Create(de265_get_image_width(img,0),
de265_get_image_height(img,0),
"psnr output");
}
win.Display(MakeImage(error_map));
}
// display SSIM error map
if (show_ssim_map) {
static X11Win win;
static bool first=true;
if (first) {
first=false;
win.Create(de265_get_image_width(img,0),
de265_get_image_height(img,0),
"ssim output");
}
win.Display(MakeImage(ssimMap));
}
#endif
ssim_frames++;
ssim_y += ssimSum;
printf("%5d %6f %6f %6f %6f\n",
framecnt,
PSNR(img_mse_y), PSNR(img_mse_cb), PSNR(img_mse_cr),
ssimSum);
free(p);
}
#ifdef WIN32
#include <time.h>
#define WIN32_LEAN_AND_MEAN
#include <winsock.h>
int gettimeofday(struct timeval *tp, void *)
{
time_t clock;
struct tm tm;
SYSTEMTIME wtm;
GetLocalTime(&wtm);
tm.tm_year = wtm.wYear - 1900;
tm.tm_mon = wtm.wMonth - 1;
tm.tm_mday = wtm.wDay;
tm.tm_hour = wtm.wHour;
tm.tm_min = wtm.wMinute;
tm.tm_sec = wtm.wSecond;
tm. tm_isdst = -1;
clock = mktime(&tm);
tp->tv_sec = (long) clock;
tp->tv_usec = wtm.wMilliseconds * 1000;
return (0);
}
#endif
#ifdef HAVE___MALLOC_HOOK
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
static void *(*old_malloc_hook)(size_t, const void *);
static void *new_malloc_hook(size_t size, const void *caller) {
void *mem;
/*
if (size>1000000) {
raise(SIGINT);
}
*/
__malloc_hook = old_malloc_hook;
mem = malloc(size);
fprintf(stderr, "%p: malloc(%zu) = %p\n", caller, size, mem);
__malloc_hook = new_malloc_hook;
return mem;
}
static void init_my_hooks(void) {
old_malloc_hook = __malloc_hook;
__malloc_hook = new_malloc_hook;
}
#if DO_MEMORY_LOGGING
void (*volatile __malloc_initialize_hook)(void) = init_my_hooks;
#endif
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#endif
int main(int argc, char** argv)
{
while (1) {
int option_index = 0;
int c = getopt_long(argc, argv, "qt:chf:o:dLB:n0vT:m:se"
#if HAVE_VIDEOGFX && HAVE_SDL
"V"
#endif
, long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'q': quiet++; break;
case 't': nThreads=atoi(optarg); break;
case 'c': check_hash=true; break;
case 'f': max_frames=atoi(optarg); break;
case 'o': write_yuv=true; output_filename=optarg; break;
case 'h': show_help=true; break;
case 'd': dump_headers=true; break;
case 'n': nal_input=true; break;
case 'V': output_with_videogfx=true; break;
case 'L': logging=false; break;
case '0': no_acceleration=true; break;
case 'B': write_bytestream=true; bytestream_filename=optarg; break;
case 'm': measure_quality=true; reference_filename=optarg; break;
case 's': show_ssim_map=true; break;
case 'e': show_psnr_map=true; break;
case 'T': highestTID=atoi(optarg); break;
case 'v': verbosity++; break;
}
}
if (optind != argc-1 || show_help) {
fprintf(stderr," dec265 v%s\n", de265_get_version());
fprintf(stderr,"--------------\n");
fprintf(stderr,"usage: dec265 [options] videofile.bin\n");
fprintf(stderr,"The video file must be a raw bitstream, or a stream with NAL units (option -n).\n");
fprintf(stderr,"\n");
fprintf(stderr,"options:\n");
fprintf(stderr," -q, --quiet do not show decoded image\n");
fprintf(stderr," -t, --threads N set number of worker threads (0 - no threading)\n");
fprintf(stderr," -c, --check-hash perform hash check\n");
fprintf(stderr," -n, --nal input is a stream with 4-byte length prefixed NAL units\n");
fprintf(stderr," -f, --frames N set number of frames to process\n");
fprintf(stderr," -o, --output write YUV reconstruction\n");
fprintf(stderr," -d, --dump dump headers\n");
#if HAVE_VIDEOGFX && HAVE_SDL
fprintf(stderr," -V, --videogfx output with videogfx instead of SDL\n");
#endif
fprintf(stderr," -0, --noaccel do not use any accelerated code (SSE)\n");
fprintf(stderr," -v, --verbose increase verbosity level (up to 3 times)\n");
fprintf(stderr," -L, --no-logging disable logging\n");
fprintf(stderr," -B, --write-bytestream FILENAME write raw bytestream (from NAL input)\n");
fprintf(stderr," -m, --measure YUV compute PSNRs relative to reference YUV\n");
#if HAVE_VIDEOGFX
fprintf(stderr," -s, --ssim show SSIM-map (only when -m active)\n");
fprintf(stderr," -e, --errmap show error-map (only when -m active)\n");
#endif
fprintf(stderr," -T, --highest-TID select highest temporal sublayer to decode\n");
fprintf(stderr," --disable-deblocking disable deblocking filter\n");
fprintf(stderr," --disable-sao disable sample-adaptive offset filter\n");
fprintf(stderr," -h, --help show help\n");
exit(show_help ? 0 : 5);
}
de265_error err =DE265_OK;
de265_decoder_context* ctx = de265_new_decoder();
de265_set_parameter_bool(ctx, DE265_DECODER_PARAM_BOOL_SEI_CHECK_HASH, check_hash);
de265_set_parameter_bool(ctx, DE265_DECODER_PARAM_SUPPRESS_FAULTY_PICTURES, false);
de265_set_parameter_bool(ctx, DE265_DECODER_PARAM_DISABLE_DEBLOCKING, disable_deblocking);
de265_set_parameter_bool(ctx, DE265_DECODER_PARAM_DISABLE_SAO, disable_sao);
if (dump_headers) {
de265_set_parameter_int(ctx, DE265_DECODER_PARAM_DUMP_SPS_HEADERS, 1);
de265_set_parameter_int(ctx, DE265_DECODER_PARAM_DUMP_VPS_HEADERS, 1);
de265_set_parameter_int(ctx, DE265_DECODER_PARAM_DUMP_PPS_HEADERS, 1);
de265_set_parameter_int(ctx, DE265_DECODER_PARAM_DUMP_SLICE_HEADERS, 1);
}
if (no_acceleration) {
de265_set_parameter_int(ctx, DE265_DECODER_PARAM_ACCELERATION_CODE, de265_acceleration_SCALAR);
}
if (!logging) {
de265_disable_logging();
}
de265_set_verbosity(verbosity);
if (argc>=3) {
if (nThreads>0) {
err = de265_start_worker_threads(ctx, nThreads);
}
}
de265_set_limit_TID(ctx, highestTID);
if (measure_quality) {
reference_file = fopen(reference_filename, "rb");
}
FILE* fh;
if (strcmp(argv[optind],"-")==0) {
fh = stdin;
}
else {
fh = fopen(argv[optind], "rb");
}
if (fh==NULL) {
fprintf(stderr,"cannot open file %s!\n", argv[optind]);
exit(10);
}
FILE* bytestream_fh = NULL;
if (write_bytestream) {
bytestream_fh = fopen(bytestream_filename, "wb");
}
bool stop=false;
struct timeval tv_start;
gettimeofday(&tv_start, NULL);
int pos=0;
while (!stop)
{
//tid = (framecnt/1000) & 1;
//de265_set_limit_TID(ctx, tid);
if (nal_input) {
uint8_t len[4];
int n = fread(len,1,4,fh);
int length = (len[0]<<24) + (len[1]<<16) + (len[2]<<8) + len[3];
uint8_t* buf = (uint8_t*)malloc(length);
n = fread(buf,1,length,fh);
err = de265_push_NAL(ctx, buf,n, pos, (void*)1);
if (write_bytestream) {
uint8_t sc[3] = { 0,0,1 };
fwrite(sc ,1,3,bytestream_fh);
fwrite(buf,1,n,bytestream_fh);
}
free(buf);
pos+=n;
}
else {
// read a chunk of input data
uint8_t buf[BUFFER_SIZE];
int n = fread(buf,1,BUFFER_SIZE,fh);
// decode input data
if (n) {
err = de265_push_data(ctx, buf, n, pos, (void*)2);
if (err != DE265_OK) {
break;
}
}
pos+=n;
if (0) { // fake skipping
if (pos>1000000) {
printf("RESET\n");
de265_reset(ctx);
pos=0;
fseek(fh,-200000,SEEK_CUR);
}
}
}
// printf("pending data: %d\n", de265_get_number_of_input_bytes_pending(ctx));
if (feof(fh)) {
err = de265_flush_data(ctx); // indicate end of stream
stop = true;
}
// decoding / display loop
int more=1;
while (more)
{
more = 0;
// decode some more
err = de265_decode(ctx, &more);
if (err != DE265_OK) {
// if (quiet<=1) fprintf(stderr,"ERROR: %s\n", de265_get_error_text(err));
if (check_hash && err == DE265_ERROR_CHECKSUM_MISMATCH)
stop = 1;
more = 0;
break;
}
// show available images
const de265_image* img = de265_get_next_picture(ctx);
if (img) {
if (measure_quality) {
measure(img);
}
stop = output_image(img);
if (stop) more=0;
else more=1;
}
// show warnings
for (;;) {
de265_error warning = de265_get_warning(ctx);
if (warning==DE265_OK) {
break;
}
if (quiet<=1) fprintf(stderr,"WARNING: %s\n", de265_get_error_text(warning));
}
}
}
fclose(fh);
if (write_bytestream) {
fclose(bytestream_fh);
}
if (measure_quality) {
printf("#total %6f %6f %6f %6f\n",
PSNR(mse_y /mse_frames),
PSNR(mse_cb/mse_frames),
PSNR(mse_cr/mse_frames),
ssim_y/ssim_frames);
fclose(reference_file);
}
de265_free_decoder(ctx);
struct timeval tv_end;
gettimeofday(&tv_end, NULL);
if (err != DE265_OK) {
if (quiet<=1) fprintf(stderr,"decoding error: %s (code=%d)\n", de265_get_error_text(err), err);
}
double secs = tv_end.tv_sec-tv_start.tv_sec;
secs += (tv_end.tv_usec - tv_start.tv_usec)*0.001*0.001;
if (quiet<=1) fprintf(stderr,"nFrames decoded: %d (%dx%d @ %5.2f fps)\n",framecnt,
width,height,framecnt/secs);
return err==DE265_OK ? 0 : 10;
}