Lucas Teske revidoval tento gist 12 years ago. Přejít na revizi
3 files changed, 274 insertions
VPXEncoder.cpp(vytvořil soubor)
| @@ -0,0 +1,172 @@ | |||
| 1 | + | /* | |
| 2 | + | * VPXEncoder.h | |
| 3 | + | * | |
| 4 | + | * Created on: 28/12/2013 | |
| 5 | + | * Author: lucas | |
| 6 | + | * Based on: http://www.webmproject.org/docs/vp8-sdk/example__simple__encoder.html | |
| 7 | + | * An part of StepFever project. | |
| 8 | + | */ | |
| 9 | + | ||
| 10 | + | ||
| 11 | + | #include "VPXEncoder.h" | |
| 12 | + | ||
| 13 | + | VPXEncoder::VPXEncoder() :in_width(0) | |
| 14 | + | ,in_height(0) | |
| 15 | + | ,out_width(0) | |
| 16 | + | ,out_height(0) | |
| 17 | + | ,in_pixel_format(AV_PIX_FMT_RGB24) | |
| 18 | + | ,out_pixel_format(AV_PIX_FMT_YUV420P) | |
| 19 | + | ,sws(NULL) | |
| 20 | + | {} | |
| 21 | + | ||
| 22 | + | void VPXEncoder::write_ivf_file_header(FILE *outfile, const vpx_codec_enc_cfg_t *cfg, int frame_cnt) { | |
| 23 | + | char header[32]; | |
| 24 | + | ||
| 25 | + | if(cfg->g_pass != VPX_RC_ONE_PASS && cfg->g_pass != VPX_RC_LAST_PASS) | |
| 26 | + | return; | |
| 27 | + | header[0] = 'D'; | |
| 28 | + | header[1] = 'K'; | |
| 29 | + | header[2] = 'I'; | |
| 30 | + | header[3] = 'F'; | |
| 31 | + | mem_put_le16(header+4, 0); /* version */ | |
| 32 | + | mem_put_le16(header+6, 32); /* headersize */ | |
| 33 | + | mem_put_le32(header+8, fourcc); /* headersize */ | |
| 34 | + | mem_put_le16(header+12, cfg->g_w); /* width */ | |
| 35 | + | mem_put_le16(header+14, cfg->g_h); /* height */ | |
| 36 | + | mem_put_le32(header+16, cfg->g_timebase.den); /* rate */ | |
| 37 | + | mem_put_le32(header+20, cfg->g_timebase.num); /* scale */ | |
| 38 | + | mem_put_le32(header+24, frame_cnt); /* length */ | |
| 39 | + | mem_put_le32(header+28, 0); /* unused */ | |
| 40 | + | ||
| 41 | + | (void) fwrite(header, 1, 32, outfile); | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | void VPXEncoder::write_ivf_frame_header(FILE *outfile, const vpx_codec_cx_pkt_t *pkt) | |
| 45 | + | { | |
| 46 | + | char header[12]; | |
| 47 | + | vpx_codec_pts_t pts; | |
| 48 | + | ||
| 49 | + | if(pkt->kind != VPX_CODEC_CX_FRAME_PKT) | |
| 50 | + | return; | |
| 51 | + | ||
| 52 | + | pts = pkt->data.frame.pts; | |
| 53 | + | mem_put_le32(header, pkt->data.frame.sz); | |
| 54 | + | mem_put_le32(header+4, pts&0xFFFFFFFF); | |
| 55 | + | mem_put_le32(header+8, pts >> 32); | |
| 56 | + | ||
| 57 | + | (void) fwrite(header, 1, 12, outfile); | |
| 58 | + | } | |
| 59 | + | void VPXEncoder::die_codec(vpx_codec_ctx_t *ctx, const char *s) { | |
| 60 | + | const char *detail = vpx_codec_error_detail(ctx); | |
| 61 | + | cout << s << ": " << vpx_codec_error(ctx) << endl; | |
| 62 | + | if(detail) | |
| 63 | + | cout << " " << detail << endl; | |
| 64 | + | exit(EXIT_FAILURE); | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | VPXEncoder::~VPXEncoder() { | |
| 68 | + | if(sws) | |
| 69 | + | close(); | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | bool VPXEncoder::open(std::string filename) { | |
| 73 | + | ||
| 74 | + | if(!vpx_img_alloc(&raw, VPX_IMG_FMT_I420, out_width, out_height, 1)) { | |
| 75 | + | cout << "Failed to allocate image " << out_width << " " << out_height << endl; | |
| 76 | + | return false; | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | if(out_pixel_format != AV_PIX_FMT_YUV420P) { | |
| 80 | + | cout << ("At this moment the output format must be AV_PIX_FMT_YUV420P") << endl; | |
| 81 | + | return false; | |
| 82 | + | } | |
| 83 | + | sws = sws_getContext(in_width, in_height, in_pixel_format, | |
| 84 | + | out_width, out_height, out_pixel_format, | |
| 85 | + | SWS_FAST_BILINEAR, NULL, NULL, NULL); | |
| 86 | + | ||
| 87 | + | if(!sws) { | |
| 88 | + | cout << "Cannot create SWS context" << endl; | |
| 89 | + | ::exit(EXIT_FAILURE); | |
| 90 | + | } | |
| 91 | + | ||
| 92 | + | outfile = fopen(filename.c_str(), "w+b"); | |
| 93 | + | if(!outfile) { | |
| 94 | + | cout << "Cannot open the VP8 destination file" << endl; | |
| 95 | + | return false; | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | res = vpx_codec_enc_config_default(interface, &cfg, 0); | |
| 99 | + | if(res) { | |
| 100 | + | cout << "Failed to get config: " << vpx_codec_err_to_string(res) << endl; | |
| 101 | + | return false; | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | cfg.rc_target_bitrate = out_width * out_height * cfg.rc_target_bitrate; | |
| 105 | + | cfg.g_w = out_width; | |
| 106 | + | cfg.g_h = out_height; | |
| 107 | + | ||
| 108 | + | write_ivf_file_header(outfile, &cfg, 0); | |
| 109 | + | if(vpx_codec_enc_init(&codec, interface, &cfg, 0)) | |
| 110 | + | die_codec(&codec, "Failed to initialize encoder"); | |
| 111 | + | ||
| 112 | + | frame_avail = 1; | |
| 113 | + | got_data = 0; | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | bool VPXEncoder::encode(char* pixels, bool ReflectY) { | |
| 117 | + | vpx_codec_iter_t iter = NULL; | |
| 118 | + | const vpx_codec_cx_pkt_t *pkt; | |
| 119 | + | ||
| 120 | + | if(!sws) { | |
| 121 | + | cout << ("Not initialized, so cannot encode") << endl; | |
| 122 | + | return false; | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | // copy the pixels into our "raw input" container. | |
| 126 | + | int bytes_filled = avpicture_fill(&pic_raw, (uint8_t*)pixels, in_pixel_format, in_width, in_height); | |
| 127 | + | if(!bytes_filled) { | |
| 128 | + | cout << ("Cannot fill the raw input buffer") << endl; | |
| 129 | + | return false; | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | // Reflect for OpenGL | |
| 133 | + | if(ReflectY) { | |
| 134 | + | for (int i = 0; i < 4; i++) { | |
| 135 | + | pic_raw.data[i] += pic_raw.linesize[i] * (in_height-1); | |
| 136 | + | pic_raw.linesize[i] = -pic_raw.linesize[i]; | |
| 137 | + | } | |
| 138 | + | } | |
| 139 | + | // convert to I420 for vp8 | |
| 140 | + | int h = sws_scale(sws, pic_raw.data, pic_raw.linesize, 0, in_height, raw.planes, raw.stride); | |
| 141 | + | ||
| 142 | + | if(h != out_height) { | |
| 143 | + | cout << "scale failed: " << h << endl; | |
| 144 | + | return false; | |
| 145 | + | } | |
| 146 | + | if(vpx_codec_encode(&codec, &raw, frame_cnt, 1, flags, VPX_DL_REALTIME)) | |
| 147 | + | die_codec(&codec, "Failed to encode frame"); | |
| 148 | + | ||
| 149 | + | while( (pkt = vpx_codec_get_cx_data(&codec, &iter)) ) { | |
| 150 | + | switch(pkt->kind) { | |
| 151 | + | case VPX_CODEC_CX_FRAME_PKT: | |
| 152 | + | write_ivf_frame_header(outfile, pkt); | |
| 153 | + | (void) fwrite(pkt->data.frame.buf, 1, pkt->data.frame.sz, outfile); | |
| 154 | + | break; | |
| 155 | + | default:break; | |
| 156 | + | } | |
| 157 | + | } | |
| 158 | + | frame_cnt++; | |
| 159 | + | return true; | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | bool VPXEncoder::close() { | |
| 163 | + | ||
| 164 | + | if(outfile) { | |
| 165 | + | if(!fseek(outfile, 0, SEEK_SET)) | |
| 166 | + | write_ivf_file_header(outfile, &cfg, frame_cnt-1); | |
| 167 | + | ||
| 168 | + | fclose(outfile); | |
| 169 | + | outfile = NULL; | |
| 170 | + | } | |
| 171 | + | return true; | |
| 172 | + | } | |
VPXEncoder.h(vytvořil soubor)
| @@ -0,0 +1,72 @@ | |||
| 1 | + | /* | |
| 2 | + | * VPXEncoder.h | |
| 3 | + | * | |
| 4 | + | * Created on: 28/12/2013 | |
| 5 | + | * Author: lucas | |
| 6 | + | * Based on: http://www.webmproject.org/docs/vp8-sdk/example__simple__encoder.html | |
| 7 | + | * An part of StepFever project. | |
| 8 | + | */ | |
| 9 | + | ||
| 10 | + | #include "../game.h" | |
| 11 | + | ||
| 12 | + | #ifndef VPXENCODER_H_ | |
| 13 | + | #define VPXENCODER_H_ | |
| 14 | + | ||
| 15 | + | //#define VPX_CODEC_DISABLE_COMPAT 1 | |
| 16 | + | ||
| 17 | + | #include "vpx/vpx_encoder.h" | |
| 18 | + | #include "vpx/vp8cx.h" | |
| 19 | + | ||
| 20 | + | #define interface (vpx_codec_vp8_cx()) | |
| 21 | + | #define fourcc 0x30385056 | |
| 22 | + | ||
| 23 | + | #define IVF_FILE_HDR_SZ (32) | |
| 24 | + | #define IVF_FRAME_HDR_SZ (12) | |
| 25 | + | ||
| 26 | + | extern "C" { | |
| 27 | + | #include <x264.h> | |
| 28 | + | #include <libswscale/swscale.h> | |
| 29 | + | #include <libavcodec/avcodec.h> | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | ||
| 33 | + | class VPXEncoder { | |
| 34 | + | private: | |
| 35 | + | static inline void mem_put_le16(char *mem, unsigned int val) { mem[0] = val; mem[1] = val>>8; } | |
| 36 | + | static inline void mem_put_le32(char *mem, unsigned int val) { mem[0] = val; mem[1] = val>>8; mem[2] = val>>16; mem[3] = val>>24; } | |
| 37 | + | static void write_ivf_file_header(FILE *outfile, const vpx_codec_enc_cfg_t *cfg, int frame_cnt); | |
| 38 | + | static void write_ivf_frame_header(FILE *outfile, const vpx_codec_cx_pkt_t *pkt); | |
| 39 | + | static void die_codec(vpx_codec_ctx_t *ctx, const char *s); | |
| 40 | + | ||
| 41 | + | public: | |
| 42 | + | VPXEncoder(); | |
| 43 | + | virtual ~VPXEncoder(); | |
| 44 | + | bool open(std::string filename); /* open for encoding */ | |
| 45 | + | bool encode(char* pixels, bool ReflectY); /* encode the given data */ | |
| 46 | + | bool encode(char* pixels) { return encode(pixels, false); }; /* encode the given data */ | |
| 47 | + | bool close(); /* close the encoder and file, frees all memory */ | |
| 48 | + | ||
| 49 | + | // Params | |
| 50 | + | ||
| 51 | + | FILE *outfile; | |
| 52 | + | vpx_codec_ctx_t codec; | |
| 53 | + | vpx_codec_enc_cfg_t cfg; | |
| 54 | + | int frame_cnt = 0; | |
| 55 | + | vpx_image_t raw; | |
| 56 | + | vpx_codec_err_t res; | |
| 57 | + | int frame_avail; | |
| 58 | + | int got_data; | |
| 59 | + | int flags = 0; | |
| 60 | + | ||
| 61 | + | int in_width; | |
| 62 | + | int in_height; | |
| 63 | + | int out_width; | |
| 64 | + | int out_height; | |
| 65 | + | AVPixelFormat in_pixel_format; | |
| 66 | + | AVPixelFormat out_pixel_format; | |
| 67 | + | AVPicture pic_raw; | |
| 68 | + | ||
| 69 | + | struct SwsContext* sws; | |
| 70 | + | }; | |
| 71 | + | ||
| 72 | + | #endif /* VPXENCODER_H_ */ | |
example.cpp(vytvořil soubor)
| @@ -0,0 +1,30 @@ | |||
| 1 | + | /* This is not a full code, just an example */ | |
| 2 | + | ||
| 3 | + | #include "video/VPXEncoder.h" | |
| 4 | + | ||
| 5 | + | VPXEncoder encoder; | |
| 6 | + | char *pixels; | |
| 7 | + | ||
| 8 | + | int main() { | |
| 9 | + | /* do blablabla */ | |
| 10 | + | encoder.in_width = 1280; // Input Image Width | |
| 11 | + | encoder.in_height = 720; // Input Image Height | |
| 12 | + | encoder.out_width = 1280; // Output Image Width | |
| 13 | + | encoder.out_height = 720; // Output Image Height | |
| 14 | + | encoder.open("teste.vpx"); // Open the output file for writting, in this example teste.vpx will be the file | |
| 15 | + | ||
| 16 | + | /* do your stuff */ | |
| 17 | + | ||
| 18 | + | while(bla) { | |
| 19 | + | // Do Stuff and put the pixels you want on char array pixels. The pixels array must be width * height * 3 (RGB Pixel data) | |
| 20 | + | // If you need to Flip Vertically (for example with an OpenGL Pixel array) use this | |
| 21 | + | encoder.encode(pixels, true); | |
| 22 | + | // If not, use this | |
| 23 | + | encoder.encode(pixels); | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | // Lets close the file and finish the vpx: | |
| 27 | + | encoder.close() | |
| 28 | + | ||
| 29 | + | return 0; | |
| 30 | + | } | |