参考ffmpeg的例子写的h264编解码显示程序

下面的代码有很多注释,而且主要是ffmpeg例子下的encode的例子,基本上没有改动,只是稍微改了一下,让他适合在vs下编译。

#include<opencv\cv.h>
#include<opencv\highgui.h>//opencv的头文件。如果不会opencv的话,应把代码改动下。
#ifdef  __cplusplus
extern "C" {
#endif
#include <math.h>

#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
void video_encode_example(const char *filename, int codec_id)
{
    AVCodec *codec;
    AVCodecContext *c= NULL;
    int i, ret, x, y, got_output;
    FILE *f;
    AVFrame *frame;
    AVPacket pkt;
    uint8_t endcode[] = { 0, 0, 1, 0xb7 };//文件的结尾 要写的几个字节。

    printf("Encode video file %s\n", filename);

    /* find the mpeg1 video encoder */
    codec = avcodec_find_encoder((AVCodecID)codec_id);//找到对应的解码器,这个codec_id是人为找到的。
    if (!codec) {//找到后返回AVCodec结构,其中codec->id==code_id
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec);//得到解码器的上下文。
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }
	IplImage *img = cvLoadImage("hello.jpg");//opencv的接口。
    /* put sample parameters */
    c->bit_rate = 400000;
    /* resolution must be a multiple of two */
	c->width = img->width;
	c->height =img->height;//影片的宽度和高度
    /* frames per second */
    //c->time_base= (AVRational){1,25};
	AVRational  a={1,25};//本人未查到这个AVRational结构体的意义。,根据上面的英文猜测,为:1秒钟播放25帧。
	c->time_base=a;
    c->gop_size = 10; /* emit one intra frame every ten frames *///GOP(Group of Pictures)策略影响编码质量:所谓GOP,意思是画面组,一个GOP就是一组连续的画面。
    c->max_b_frames=1;
    c->pix_fmt = AV_PIX_FMT_YUV420P;//影片的编码格式,这是4:2:0的yuv格式。

    if(codec_id == AV_CODEC_ID_H264)
        av_opt_set(c->priv_data, "preset", "slow", 0);

    /* open it */
    if (avcodec_open2(c, codec, NULL) < 0) {//打开这个H264解码器
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    f = fopen(filename, "wb");//打开影片的文件。设为写(write);
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    frame = avcodec_alloc_frame();//给frame结构体分配内存。,但并为给frame->data分配内存。
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    frame->format = c->pix_fmt;//格式
    frame->width  = c->width;//宽和高
    frame->height = c->height;

    /* the image can be allocated by any means and av_image_alloc() is
     * just the most convenient way if av_malloc() is to be used */
    ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height,
                         c->pix_fmt, 32);//实际分配内存。与前面的avcodec_alloc_frame()函数配合使用。
    if (ret < 0) {//
        fprintf(stderr, "Could not allocate raw picture buffer\n");//分配内存失败。
        exit(1);
    }

    /* encode 1 second of video */
    for(i=0;i<200;i++) {//这里写入200帧的数据。200/25=8,,,因此产生了8s的影片。
        av_init_packet(&pkt);//自然初始化了,
        pkt.data = NULL;    // packet data will be allocated by the encoder、、如果没有这个=null这个,会在vs中产生0xdddddddd错误。
        pkt.size = 0;

        fflush(stdout);//
		IplImage *img = cvLoadImage("hello.jpg");//cv接口。
		cvCvtColor(img,img,CV_BGR2YCrCb);
		//frame->data=img->imageData;
        /* prepare a dummy image */
        /* Y */
		//因为我对ycrcb图像的格式弄的不是很清楚,所以,下面的大噶意思是产生个图片。
        for(y=0;y<c->height;y++) {
			uchar *ptr = (uchar *)(img->imageData+y*img->widthStep);
			for(x=0;x<c->width;x++) {
                //frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
				frame->data[0][y * frame->linesize[0] + x] = ptr[x*3+1];
            }
        }

        /* Cb and Cr */
        for(y=0;y<c->height/2;y++) {
			uchar *ptr = (uchar *)(img->imageData+y*img->widthStep);
            for(x=0;x<c->width/2;x++) {
                //frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
                //frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 2;
				frame->data[1][y * frame->linesize[1] + x]=ptr[x];
				frame->data[2][y * frame->linesize[1] + x]=ptr[x+1];
            }
        }

        frame->pts = i;

        /* encode the image */
        ret = avcodec_encode_video2(c, &pkt, frame, &got_output);//具体编码、把编码后的数据写入pkt.data中。
        if (ret < 0) {	
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        }

        if (got_output) {//如果got_output为真,就说明经过编码得到了数据。
            printf("Write frame %3d (size=%5d)\n", i, pkt.size);
            fwrite(pkt.data, 1, pkt.size, f);//把编码数据写入影片。
            av_free_packet(&pkt);
        }
    }

    /* get the delayed frames *///我不是很理解下面这个for循环,网友”@Mr.Poison “的帮助下,在有些协议中,有了后面的帧才可以编码前面的帧。以264协议为例:/有Ipb三种帧。其中i帧帧内编码,p帧单独预测编码。b帧根据ip帧编码,因此B帧只有得到p帧才能够编码。而从时间上,p帧在B帧后面。因此存在延迟帧--B帧。因此需要处理延迟的帧,所以有了这个for循环。
	//欢迎来帮助我进步。
    for (got_output = 1; got_output; i++) {
        fflush(stdout);

        ret = avcodec_encode_video2(c, &pkt, NULL, &got_output);
        if (ret < 0) {
            fprintf(stderr, "Error encoding frame\n");
            exit(1);
        }

        if (got_output) {
            printf("Write frame %3d (size=%5d)\n", i, pkt.size);
            fwrite(pkt.data, 1, pkt.size, f);
            av_free_packet(&pkt);
        }
    }

    /* add sequence end code to have a real mpeg file */
    fwrite(endcode, 1, sizeof(endcode), f);//写结尾。
    fclose(f);//关闭f文件流
	//清理内存。
    avcodec_close(c);
    av_free(c);
    av_freep(&frame->data[0]);
    avcodec_free_frame(&frame);
    printf("\n");
}
//现象:在你的文件夹下出现一个test.h264的视频文件。用暴风影音可以打开。
int main()
{
	avcodec_register_all();//必须加这个函数,我经常吃没有这个函数的亏。
	const char *output_type ;
	output_type = "h264";
	video_encode_example("test.h264",AV_CODEC_ID_H264);
	return 0;
}


#ifdef  __cplusplus
}
#endif



下面的例子是写的解码MPEG1文件的解码程序,并把解码的帧存到h文件夹中。

例子中有注释。

#include<opencv\cv.h>
#include<opencv\highgui.h>
#ifdef  __cplusplus
extern "C" {
#endif
#include <math.h>

#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>

#define INBUF_SIZE 4096    //4k字节。
	static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
                     char *filename)
{
    FILE *f;
    int i;

    f=fopen(filename,"w");
    fprintf(f,"P5\n%d %d\n%d\n",xsize,ysize,255);
    for(i=0;i<ysize;i++)
        fwrite(buf + i * wrap,1,xsize,f);
    fclose(f);
}
static int decode_write_frame(const char *outfilename, AVCodecContext *avctx,
                              AVFrame *frame, int *frame_count, AVPacket *pkt, int last)
{
    int len, got_frame;
    char buf[1024];

    len = avcodec_decode_video2(avctx, frame, &got_frame, pkt);//解码MPEG1文件。并把解码的数据存到frame中。
    if (len < 0) {
        fprintf(stderr, "Error while decoding frame %d\n", *frame_count);
        return len;
    }
    if (got_frame) {//成功的解码了。
        printf("Saving %sframe %3d\n", last ? "last " : "", *frame_count);
        fflush(stdout);

        /* the picture is allocated by the decoder, no need to free it */
        _snprintf(buf, sizeof(buf), outfilename, *frame_count);
        pgm_save(frame->data[0], frame->linesize[0],
                 avctx->width, avctx->height, buf);
        (*frame_count)++;
    }
	//下面注释内容猜测。
	//1.如果pkt中的内容(即data指针所指的数据),不够一帧,或者帧数不够解码的,例如在h264协议中必须得到p帧后才能够解码。
			//则把pkt-)data指针向后移,为了防止把未解码的数据掩盖。
	//2.如果pkt的内容已经解码,那么这个pkt-》data指针应该是null,不执行下面的if语句中的内容。
    if (pkt->data) {
        pkt->size -= len;
        pkt->data += len;
    }
    return 0;
}

static void video_decode_example(const char *outfilename, const char *filename)
{
    AVCodec *codec;
    AVCodecContext *c= NULL;
    int frame_count;
    FILE *f;
    AVFrame *frame;
    uint8_t inbuf[INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
    AVPacket avpkt;

    av_init_packet(&avpkt);

    /* set end of buffer to 0 (this ensures that no overreading happens for damaged mpeg streams) */
    memset(inbuf + INBUF_SIZE, 0, FF_INPUT_BUFFER_PADDING_SIZE);

    printf("Decode video file %s to %s\n", filename, outfilename);

    /* find the mpeg1 video decoder */
    codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO);//查找mpeg1的解码器
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    c = avcodec_alloc_context3(codec);///查找MPEG1的解码器上下文
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    if(codec->capabilities&CODEC_CAP_TRUNCATED)
        c->flags|= CODEC_FLAG_TRUNCATED; /* we do not send complete frames */

    /* 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 = avcodec_alloc_frame();//给这个frame分配一块头内存。
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    frame_count = 0;
    for(;;) {
        avpkt.size = fread(inbuf, 1, INBUF_SIZE, f);//把要读的文件读入inbuf中,
        if (avpkt.size == 0)
            break;

        /* NOTE1: some codecs are stream based (mpegvideo, mpegaudio)
           and this is the only method to use them because you cannot
           know the compressed data size before analysing it.

           BUT some other codecs (msmpeg4, mpeg4) are inherently frame
           based, so you must call them with all the data for one
           frame exactly. You must also initialize 'width' and
           'height' before initializing them. */

        /* NOTE2: some codecs allow the raw parameters (frame size,
           sample rate) to be changed at any frame. We handle this, so
           you should also take care of it */

        /* here, we use a stream based decoder (mpeg1video), so we
           feed decoder and see if it could decode a frame */
        avpkt.data = inbuf;//自然是把data指针指向inbuf这个缓冲区了。AVPacket这个时候就有意义了。
        while (avpkt.size > 0)
            if (decode_write_frame(outfilename, c, frame, &frame_count, &avpkt, 0) < 0)
                exit(1);
    }

    /* some codecs, such as MPEG, transmit the I and P frame with a
       latency of one frame. You must do the following to have a
       chance to get the last frame of the video */
    avpkt.data = NULL;
    avpkt.size = 0;
    decode_write_frame(outfilename, c, frame, &frame_count, &avpkt, 1);

    fclose(f);

    avcodec_close(c);
    av_free(c);
    avcodec_free_frame(&frame);
    printf("\n");
}
void main()
{
	avcodec_register_all();//codec全部初始化
	video_decode_example(".\\h\\test%02d.pgm", "test.mpg");//打开文件
}

#ifdef  __cplusplus
}
#endif

在参考http://www.cnblogs.com/nanguabing/archive/2012/04/12/2443314.html的文章写的注释。我发现这个程序会有丢帧的现象,但没有找到解决的办法。

主要功能是使用api打开文件,并解码。

#include<opencv\cv.h>
#include<opencv\highgui.h>
#ifdef  __cplusplus
extern "C" {
#endif
#include<libavcodec\avcodec.h>
#include<libavformat\avformat.h>
#include<libswscale\swscale.h>
#include<stdio.h>
	
	void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
		FILE *pFile;
		const char *szFilename = "D.txt";
		int y;

		// Open file
		pFile = fopen(szFilename, "a+");
		if (pFile == NULL)
			return;

		// Write header
		//printf("P6\n%d %d\n255\n", width, height);
		printf("bbbbbbbbbb\n");
		// Write pixel data
		for (y = 0; y < height; y++)
			fwrite(pFrame->data[0] + y * pFrame->linesize[0], sizeof(char),
					width * 3, pFile);

		// Close file
		fclose(pFile);
	}
	int  main()
	{
		AVFormatContext *pFormatCtx=NULL;
		int i ,videoStream;
		AVCodecContext *pCodecCtx=NULL;
		AVCodec *pCodec=NULL;
		AVFrame *pFrame=NULL;
		AVFrame *pFrameRGB=NULL;
		AVPacket packet;
		int frameFinished = 0;
		int numBytes;
		uint8_t *buffer=NULL;
		struct SwsContext *pSwsCtx;
		av_register_all();
		const char *filename = "test.h264";//要打开的视频文件。
		if(avformat_open_input(&pFormatCtx,filename,NULL,NULL))//打开文件。现在这个视频压缩文件的“句柄”(我也不知道该怎么翻译。),就存在于pFormatCtx中
			return 1;
		if(av_find_stream_info(pFormatCtx) <0)//检查文件头中的流信息。
			return 2;
		av_dump_format(pFormatCtx,0,filename,0);//负责为pFormatCtx->streams填充上正确的信息。

		videoStream = -1;
		for(i=0;i<pFormatCtx->nb_streams;i++)//是为了找到视频流。剔除音频流
		{
			if(pFormatCtx->streams[i]->codec->codec_type== AVMEDIA_TYPE_VIDEO)
			{
				videoStream =i;
				break;
			}//IF
		}//for
		if(videoStream ==-1)//查找视频流失败,
			return 3;

		pCodecCtx = pFormatCtx->streams[videoStream]->codec;//得到解码器上下文信息。
		pCodec= avcodec_find_decoder(pCodecCtx->codec_id);//de得到真正的解码器。
		if(pCodec==NULL)
		{
			fprintf(stderr,"unsupporte codec");
			return 4;
		}
		if(avcodec_open(pCodecCtx,pCodec)<0)//打开解码器。
			return 5;
		pFrame = avcodec_alloc_frame();// 申请pframe内存。始化的时候AVFrame中的元素data,linesize均为空。未指向任何内存数据。
		pFrameRGB	= avcodec_alloc_frame();//rgb格式的frame。
		if(pFrameRGB==NULL	||pFrame ==NULL)
			return 6;
		numBytes = avpicture_get_size(PIX_FMT_RGB24,pCodecCtx->width,
			pCodecCtx->height);//计算需要分配内存的大小。
		buffer	=(uint8_t*)av_malloc(numBytes * sizeof(uint8_t));//申请frame的data指针的内存。

		avpicture_fill((AVPicture*)pFrameRGB,buffer,PIX_FMT_RGB24,
			pCodecCtx->width,pCodecCtx->height);//把帧和pFrameRGB关联起来。jiu是把buffer哪块内存分配给pFrameRGB

		i=0;
		while(av_read_frame(pFormatCtx,&packet) >=0)//读取pFormatCtx中的数据岛packet中。
		{
			if(packet.stream_index==videoStream)//测试一下,这个包是不是视频流。
			{
				pFrame= avcodec_alloc_frame();//
				int w= pCodecCtx->width;
				int h = pCodecCtx->height;
				avcodec_decode_video2(pCodecCtx,pFrame,&frameFinished,
					&packet);//解码这个包。
				pSwsCtx	=	sws_getContext(w,h,pCodecCtx->pix_fmt,w,h,
						PIX_FMT_RGB565,SWS_POINT,NULL,NULL,NULL);//负责得到视频分辩率、色彩空间变换时所需要的上下文句柄。
				if(frameFinished)
				{
					sws_scale(pSwsCtx,pFrame->data,pFrame->linesize,0
						,pCodecCtx->height,pFrameRGB->data,pFrameRGB->linesize);//z转换成rgb格式。
					++i;
					printf("%d\n",i);
					SaveFrame(pFrameRGB,pCodecCtx->width,pCodecCtx->height,i);
				}//frmaeFinshed;
			}//if
			av_free_packet(&packet);

		}//while

		经我测试这个程序最后会丢帧。我测试200帧,但是会丢最后的2帧(h264格式)
		av_free(buffer);
		av_free(pFrameRGB);
		av_free(pFrame);
		printf("ttttttt%d",i);
		avcodec_close(pCodecCtx);
		av_close_input_file(pFormatCtx);
		return 0;

	}//main

#ifdef  __cplusplus
}
#endif

下面的代码是使用解码,并使用sdl1.2显示出来。注意由于sdl2.0与1.2不兼容,所以,不能在2.0下运行,下面的程序参考博文:http://www.cnblogs.com/nanguabing/archive/2012/04/12/2443724.html   来编写,感谢原作者的无私奉献。

#include<opencv\cv.h>
#include<opencv\highgui.h>
#ifdef  __cplusplus
extern "C" {
#endif
#include<libavcodec\avcodec.h>
#include<libavformat\avformat.h>
#include<libswscale\swscale.h>
#include<stdio.h>
#include<SDL_thread.h>
#include<SDL.h>
	
	int  main()
	{
		AVFormatContext *pFormatCtx=NULL;
		int i ,videoStream;
		AVCodecContext *pCodecCtx=NULL;
		AVCodec *pCodec=NULL;
		AVFrame *pFrame=NULL;
		AVFrame *pFrameRGB=NULL;
		AVPacket packet;
		int frameFinished = 0;
		int numBytes;
		uint8_t *buffer=NULL;
		struct SwsContext *pSwsCtx;
		av_register_all();
		const char *filename = "test.h264";//要打开的视频文件。
		if(avformat_open_input(&pFormatCtx,filename,NULL,NULL))//打开文件。现在这个视频压缩文件的“句柄”(我也不知道该怎么翻译。),就存在于pFormatCtx中
			return 1;
		if(av_find_stream_info(pFormatCtx) <0)//检查文件头中的流信息。
			return 2;
		av_dump_format(pFormatCtx,0,filename,0);//负责为pFormatCtx->streams填充上正确的信息。

		videoStream = -1;
		for(i=0;i<pFormatCtx->nb_streams;i++)//是为了找到视频流。剔除音频流
		{
			if(pFormatCtx->streams[i]->codec->codec_type== AVMEDIA_TYPE_VIDEO)
			{
				videoStream =i;
				break;
			}//IF
		}//for
		if(videoStream ==-1)//查找视频流失败,
			return 3;

		pCodecCtx = pFormatCtx->streams[videoStream]->codec;//得到解码器上下文信息。
		pCodec= avcodec_find_decoder(pCodecCtx->codec_id);//de得到真正的解码器。
		if(pCodec==NULL)
		{
			fprintf(stderr,"unsupporte codec");
			return 4;
		}
		if(avcodec_open(pCodecCtx,pCodec)<0)//打开解码器。
			return 5;
		pFrame = avcodec_alloc_frame();// 申请pframe内存。始化的时候AVFrame中的元素data,linesize均为空。未指向任何内存数据。
		pFrameRGB	= avcodec_alloc_frame();//rgb格式的frame。
		if(pFrameRGB==NULL	||pFrame ==NULL)
			return 6;
		numBytes = avpicture_get_size(PIX_FMT_RGB24,pCodecCtx->width,
			pCodecCtx->height);//计算需要分配内存的大小。
		buffer	=(uint8_t*)av_malloc(numBytes * sizeof(uint8_t));//申请frame的data指针的内存。

		avpicture_fill((AVPicture*)pFrameRGB,buffer,PIX_FMT_RGB24,
			pCodecCtx->width,pCodecCtx->height);//把帧和pFrameRGB关联起来。jiu是把buffer哪块内存分配给pFrameRGB

		i=0;
		while(av_read_frame(pFormatCtx,&packet) >=0)//读取pFormatCtx中的数据岛packet中。
		{
			if(packet.stream_index==videoStream)//测试一下,这个包是不是视频流。
			{
				pFrame= avcodec_alloc_frame();//
				int w= pCodecCtx->width;
				int h = pCodecCtx->height;
				avcodec_decode_video2(pCodecCtx,pFrame,&frameFinished,
					&packet);//解码这个包。
				pSwsCtx	=	sws_getContext(w,h,pCodecCtx->pix_fmt,w,h,
						PIX_FMT_RGB565,SWS_POINT,NULL,NULL,NULL);//负责得到视频分辩率、色彩空间变换时所需要的上下文句柄。
				if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO |SDL_INIT_TIMER))
				{
					fprintf(stderr,"sdl%s ",SDL_GetError());
					return 7;
				}
				SDL_Surface *screen ;
				screen = SDL_SetVideoMode(pCodecCtx->width,pCodecCtx->height,0,0);
				if(!screen)
				{
					return 8;
				}
				SDL_Overlay *bmp ;
				bmp = SDL_CreateYUVOverlay(pCodecCtx->width,pCodecCtx->height,
					SDL_YV12_OVERLAY,screen);
				SDL_Rect rect;

				if(frameFinished)
				{
					SDL_LockYUVOverlay(bmp);
					AVPicture pict;
					pict.data[0]= bmp->pixels[0];
					pict.data[1]=bmp->pixels[2];
					pict.data[2]=bmp->pixels[1];
					pict.linesize[0]=bmp->pitches[0];
					pict.linesize[1]=bmp->pitches[2];
					pict.linesize[2]=bmp->pitches[1];
					img_convert(&pict,PIX_FMT_YUV420P,(AVPicture*)pFrame,
						pCodecCtx->pix_fmt,pCodecCtx->width,
						pCodecCtx->height);
					SDL_UnlockYUVOverlay(bmp);
					rect.x=0;
					rect.y=0;
					rect.w=pCodecCtx->width;
					rect.h=pCodecCtx->height;
					SDL_DisplayYUVOverlay(bmp,&rect);
				}//frmaeFinshed;
				SDL_Event event;
				av_free_packet(&packet);
				SDL_PollEvent(&event);
				switch(event.type)
				{
				case SDL_QUIT:
					SDL_Quit();
					exit(0);
					break;
				default:
					break;
				}
			}//if
			

		}//while

		经我测试这个程序最后会丢帧。我测试200帧,但是会丢最后的2帧(h264格式)
		av_free(buffer);
		av_free(pFrameRGB);
		av_free(pFrame);
		printf("ttttttt%d",i);
		avcodec_close(pCodecCtx);
		av_close_input_file(pFormatCtx);
		return 0;

	}//main

#ifdef  __cplusplus
}
#endif



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值