Windows下FFMPEG调用测试(转码加水印)
使用FFMPEG的avcodec,解码加水印,编码
注意是软编解码,延时会比较大,只是一个例子
demo比较乱,仅示例
#include <stdio.h> #include <stdlib.h> #include <string.h> extern "C" { #include <libavcodec/avcodec.h> #include <libavutil/frame.h> #include <libavcodec/avcodec.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> char av_error[AV_ERROR_MAX_STRING_SIZE] = { 0 }; #define av_err2str(errnum) av_make_error_string(av_error, AV_ERROR_MAX_STRING_SIZE, errnum) #define INBUF_SIZE 4096 static FILE* fout; static FILE* foutH264; AVCodecContext* enc_ctx = NULL; AVPacket* pkt2 = av_packet_alloc(); const char* fileOutH264name = "D:/Emscripten/cmaketest/123523.264"; static void pgm_save(AVFrame* frame, int xsize, int ysize, const char* filename) { int i; if (!fout) { fout = fopen(filename, "wb"); } //linesize中存储的是宽度,不是总长度 //写入顺序,Y V U =YV12 ;解码后的数据是YUV顺序存储的 for (i = 0; i < ysize; i++) fwrite(frame->data[0] + i * frame->linesize[0], 1, xsize, fout); for (i = 0; i < ysize / 2; i++) fwrite(frame->data[2] + i * frame->linesize[2], 1, xsize / 2, fout); for (i = 0; i < ysize / 2; i++) fwrite(frame->data[1] + i * frame->linesize[1], 1, xsize / 2, fout); } /** * 为YUV420P格式的AVFrame添加水印 * @param frame 目标帧(需确保为YUV420P格式) * @param watermark_y Y分量水印数据 * @param watermark_w 水印宽度 * @param watermark_h 水印高度 * @param start_x 水印左上角X坐标 * @param start_y 水印左上角Y坐标 * @param alpha 透明度(0.0~1.0,1为完全不透明) */ void add_watermark_to_yuv420p( AVFrame* frame, uint8_t* watermark_y, int watermark_w, int watermark_h, int start_x, int start_y, float alpha ) { // ----------------- Y 分量处理 ----------------- for (int y = 0; y < watermark_h; y++) { // 计算目标行位置(防止越界) int dst_y = start_y + y; if (dst_y >= frame->height) break; // 获取Y平面行指针 uint8_t* dst_line = frame->data[0] + dst_y * frame->linesize[0]; for (int x = 0; x < watermark_w; x++) { int dst_x = start_x + x; if (dst_x >= frame->width) break; // Y分量混合:alpha混合算法 uint8_t src_y = watermark_y[y * watermark_w + x]; dst_line[dst_x] = (uint8_t)(src_y * alpha); } } //不需要修改UV分量 } static void encode(AVCodecContext* enc_ctx, AVFrame* frame, AVPacket* pkt) { static int index = 0; int ret; if (foutH264 == NULL) { foutH264 = fopen(fileOutH264name, "wb"); if (!foutH264) { fprintf(stderr, "Could not open %s\n", fileOutH264name); exit(1); } } frame->pts += index; index++; /* send the frame to the encoder */ if (frame) printf("Send frame %3ld\n", frame->pts); ret = avcodec_send_frame(enc_ctx, frame); if (ret < 0) { fprintf(stderr, "Error sending a frame for encoding\n"); exit(1); } while (ret >= 0) { ret = avcodec_receive_packet(enc_ctx, pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) return; else if (ret < 0) { fprintf(stderr, "Error during encoding\n"); exit(1); } //printf("Write packet %3ld (size=%5d)\n", pkt->pts, pkt->size); int ref = fwrite(pkt->data, 1, pkt->size, foutH264); printf("write size :%d\n",ref); fflush(foutH264); av_packet_unref(pkt); } } static void initEnc_1() { const AVCodec* codec; const char* codec_name = "libx264"; //查找编码器;还可以使用AVcodec_find_encoder(condec_ID) codec = avcodec_find_encoder_by_name(codec_name); if (!codec) { fprintf(stderr, "Codec '%s' not found\n", codec_name); exit(1); } enc_ctx = avcodec_alloc_context3(codec); if (!enc_ctx) { fprintf(stderr, "Could not allocate video codec context\n"); exit(1); } /* put sample parameters */ enc_ctx->bit_rate = 400000; /* resolution must be a multiple of two */ enc_ctx->width = 1920; enc_ctx->height = 1080; /* frames per second */ enc_ctx->time_base.num = 1; enc_ctx->time_base.den = 25;//时间基1/25 enc_ctx->framerate.num = 25; //fps=25 enc_ctx->framerate.den = 1; enc_ctx->gop_size = 30; enc_ctx->max_b_frames = 1; enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;//YV 12 格式 if (codec->id == AV_CODEC_ID_H264) av_opt_set(enc_ctx->priv_data, "preset", "slow", 0); /* open it */ int ret = avcodec_open2(enc_ctx, codec, NULL); if (ret < 0) { fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret)); exit(1); } } static void decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt, const char* filename) { char buf[1024]; int ret; ret = avcodec_send_packet(dec_ctx, pkt); if (ret < 0) { fprintf(stderr, "Error sending a packet for decoding\n"); return;
//这里不要exit,因为你有时候可能解析的是非IDR的多slice帧,并且找不到SPS,那就要解码器吧一屏所有的slice解码完才会有输出; } while (ret >= 0) { ret = avcodec_receive_frame(dec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) return; else if (ret < 0) { fprintf(stderr, "Error during decoding\n"); exit(1); } printf("saving frame %3ld\n", dec_ctx->frame_num); printf("YUV pts:%ld %ld ,%ld\n", frame->pts,frame->pict_type); fflush(stdout); //添加水印 // 定义水印参数 // 生成100x100的"大"文字水印(Y分量) uint8_t watermark_y[100 * 100]; memset(watermark_y, 0x3F, sizeof(watermark_y)); // -----------------绘制水印 ----------------- for (int y = 20; y < 40; y++) { // 横 for (int x = 20; x < 80; x++) { watermark_y[y * 100 + x] = 0x00; } } add_watermark_to_yuv420p( frame, watermark_y, 100, 100, 0, 0, 1.0f // 70%不透明度 ); // pgm_save(frame, frame->width, frame->height, filename); //编码 // frame->pts += 1;//时间基 1/25 if (!pkt2) exit(1); encode(enc_ctx, frame, pkt2); } } static int InPutH264data() { } int main(int argc, char** argv) { const char* filename, * outfilename; const AVCodec* codec; AVCodecParserContext* parser; AVCodecContext* c = NULL; FILE* f; AVFrame* frame; uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE]; uint8_t* data; size_t data_size; int ret; int eof; AVPacket* pkt; //初始化编码 initEnc_1(); //注意直接解码要从I帧开始,所以如果H264开头不是关键帧是要报错的 filename = "D:/Emscripten/cmaketest/catch.264"; outfilename = "D:/Emscripten/cmaketest/catch_out123.yuv"; pkt = av_packet_alloc(); if (!pkt) exit(1); /* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */ memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE); /* find the MPEG-1 video decoder */ codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { fprintf(stderr, "Codec not found\n"); exit(1); } //用来给H264断帧 parser = av_parser_init(codec->id); if (!parser) { fprintf(stderr, "parser not found\n"); exit(1); } c = avcodec_alloc_context3(codec); if (!c) { fprintf(stderr, "Could not allocate video codec context\n"); exit(1); } /* For some codecs, such as msmpeg4 and mpeg4, width and height MUST be initialized there because this information is not available in the bitstream. */ /* open it */ if (avcodec_open2(c, codec, NULL) < 0) { fprintf(stderr, "Could not open codec\n"); exit(1); } f = fopen(filename, "rb"); if (!f) { fprintf(stderr, "Could not open %s\n", filename); exit(1); } frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Could not allocate video frame\n"); exit(1); } do { /* read raw data from the input file */ data_size = fread(inbuf, 1, INBUF_SIZE, f); if (ferror(f)) break; eof = !data_size; /* use the parser to split the data into frames */ data = inbuf;
//如果读文件的时候帧带长度字段可以直接赋值给pkt不需要解析断帧 while (data_size > 0 || eof) { ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); if (ret < 0) { fprintf(stderr, "Error while parsing\n"); exit(1); } data += ret; data_size -= ret; if (pkt->size) decode(c, frame, pkt, outfilename); else if (eof) break; } } while (!eof); /* flush the decoder */ decode(c, frame, NULL, outfilename); fclose(f); av_parser_close(parser); avcodec_free_context(&c); av_frame_free(&frame); av_packet_free(&pkt); if (fout) { fclose(fout); fout = nullptr; } if (foutH264) { fclose(foutH264); foutH264 = nullptr; } return 0; } }
非IDR的H264;竖向多slice;(有时候安卓还有横向的多slice)