项目需求开发网络流媒体播放器,整个项目就一人开发,整个系统平台,包括调度,存储集群服务,影像同步导入、系统api接口、视频查询和播放客户端,一切有了python就变得简单。一个人在战斗,还是蛮有劲道。
pyqt开发播放界面,python用于网络通信和粘合逻辑,ffmpeg用于解码,google一圈试用了几个pyffmpeg的库,非常不理想,干脆自己做:
采用ctypes访问dll,测试解码并将视频帧转换RGB24写入PPM文件,imagemagick转换为jpg,okay。。
C动态库
结构定义:
1 #define FF_BOOL int
2 #define FF_TRUE 1
3 #define FF_FALSE 0
4
5 typedef unsigned char StreamByte_t;
6
7 //视频流信息
8 struct MediaStreamInfo_t{
9 int codec_type;
10 int codec_id;
11 int width;
12 int height;
13 int gopsize;
14 int pixfmt;
15 int tb_num;
16 int tb_den;
17 int bitrate;
18 int frame_number;
19 int videostream; //视频流编号
20 };
21
22 struct MediaVideoFrame_t{
23 StreamByte_t * rgb24;
24 size_t size;
25 int width;
26 int height;
27 unsigned int sequence; //控制播放顺序
28 unsigned int duration; //播放时间
29 };
30
31 struct MediaPacket_t{
32 StreamByte_t* data;
33 size_t size;
34 AVPacket * pkt;
35 int stream; //流编号
36 int dts;
37 int pts;
38 size_t sequence;
39 size_t duration;
40
41 };
42
43 struct MediaFormatContext_t;
44
45 //解码器
46 struct MediaCodecContext_t{
47 AVCodecContext * codecCtx; //AVCodecContext*
48 AVCodec * codec;
49 int stream; //流编号
50 AVFrame * rgbframe24; //
51 AVFrame* frame; //
52 StreamByte_t* buffer;
53 size_t bufsize;
54 void * user;
55 MediaStreamInfo_t si;
56 };
57
58 struct MediaFormatContext_t{
59 AVFormatContext * fc; //AVFormatContext*
60 MediaStreamInfo_t video; //视频信息
61
62 };
实现部分
1
2 MEDIACODEC_API FF_BOOL InitLib(){
3 avcodec_init();
4 av_register_all();
5 return FF_TRUE;
6 }
7
8 MEDIACODEC_API void Cleanup(){
9
10 }
11
12 MEDIACODEC_API MediaCodecContext_t* InitAvCodec(MediaStreamInfo_t* si){
13 MediaCodecContext_t* ctx;
14 AVCodecContext * codecCtx;
15
16
17 codecCtx = avcodec_alloc_context();
18 codecCtx->width = si->width;
19 codecCtx->height = si->height;
20 codecCtx->time_base.num = si->tb_num;
21 codecCtx->time_base.den = si->tb_den;
22 codecCtx->bit_rate = si->bitrate;
23 codecCtx->frame_number = si->frame_number;
24 codecCtx->codec_type =(AVMediaType) si->codec_type;
25 codecCtx->codec_id = (CodecID)si->codec_id;
26
27 AVCodec *codec;
28 codec = avcodec_find_decoder(codecCtx->codec_id);
29 if(codec == NULL){
30 //avcodec_free_context()
31 return NULL;
32 }
33 if(avcodec_open(codecCtx, codec)<0){
34 return NULL;
35 }
36
37 ctx = new MediaCodecContext_t();
38 ctx->codecCtx = codecCtx;
39 ctx->codec = codec;
40 ctx->stream = si->videostream;
41 ctx->si = *si;
42
43 uint8_t *buffer;
44 size_t numBytes;
45 ctx->rgbframe24 = avcodec_alloc_frame();
46 ctx->frame = avcodec_alloc_frame();
47
48 numBytes=avpicture_get_size(PIX_FMT_RGB24, codecCtx->width,codecCtx->height);
49 buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
50 ctx->buffer = (StreamByte_t*)buffer;
51 ctx->bufsize = numBytes;
52
53 avpicture_fill((AVPicture *)ctx->rgbframe24, buffer, PIX_FMT_RGB24,codecCtx->width, codecCtx->height); // bind buffer with frame
54
55 return ctx;
56 }
57
58
59 MEDIACODEC_API void FreeAvCodec(MediaCodecContext_t* ctx){
60 AVCodecContext * codecCtx;
61 AVCodec *codec;
62 codecCtx = (AVCodecContext*) ctx->codecCtx;
63 codec = (AVCodec*) ctx->codec;
64 avcodec_close(codecCtx);
65
66 av_free(ctx->buffer);
67 av_free(ctx->rgbframe24);
68 av_free(ctx->frame);
69 delete ctx;
70 }
71
72
73 //图像格式转换
74 int convert_picture(AVFrame *rgbframe, AVFrame *frame, int width, int height, PixelFormat format,PixelFormat toformat){
75 struct SwsContext *sws;
76
77 if (format == PIX_FMT_YUV420P){
78 sws = sws_getContext(width, height, format, width, height, toformat, SWS_FAST_BILINEAR, NULL, NULL, NULL);
79 if (sws == 0){
80 return -1;
81 }
82 if (sws_scale(sws, frame->data, frame->linesize, 0, height, rgbframe->data, rgbframe->linesize)){
83 //return -1;
84 }
85 }else{
86 return -1;
87 }
88 return 0;
89 }
90
91 MEDIACODEC_API MediaVideoFrame_t * DecodeVideoFrame(MediaCodecContext_t* ctx,MediaPacket_t* pkt){
92 MediaVideoFrame_t * frame;
93
94 if(pkt->stream != ctx->stream){
95 return NULL;
96 }
97 int frameFinished;
98
99 AVCodecContext* codecCtx;
100 codecCtx = (AVCodecContext*)ctx->codecCtx;
101
102 avcodec_decode_video(codecCtx, ctx->frame, &frameFinished,pkt->data, pkt->size);
103 if( !frameFinished ){
104 return NULL;
105 }
106 int r;
107 r = convert_picture(ctx->rgbframe24,ctx->frame,codecCtx->width,codecCtx->height,codecCtx->pix_fmt,PIX_FMT_RGB24);
108 if(r){
109 return NULL;
110 }
111 // 复制一份
112 frame = new MediaVideoFrame_t();
113 frame->rgb24 = (StreamByte_t*)malloc(ctx->bufsize);
114 frame->size =ctx->bufsize;
115 memcpy(frame->rgb24,ctx->buffer,ctx->bufsize);
116 frame->width = ctx->frame->width;
117 frame->height = ctx->frame->height;
118 frame->sequence = pkt->sequence;
119 frame->duration = pkt->duration;
120
121 return frame;
122 }
123
124 MEDIACODEC_API void FreeVideoFrame(MediaVideoFrame_t* frame){
125 if(frame->rgb24){
126 free(frame->rgb24);
127 }
128 delete frame;
129 }
130
131 MEDIACODEC_API MediaPacket_t * AllocPacket(){
132 MediaPacket_t *pkt;
133
134 pkt = new MediaPacket_t();
135 pkt->pkt =new AVPacket();
136 pkt->size = 0;
137 pkt->data = NULL;
138 pkt->stream = -1;
139 return pkt;
140 }
141
142 // s - 0 : 不释放内部的 av_free_packet(); 1 - 释放packet内部数据
143 MEDIACODEC_API void FreePacket(MediaPacket_t* pkt,int s){
144 AVPacket* packet;
145 packet = pkt->pkt;
146 if(s){
147 av_free_packet(packet);
148 }
149 delete packet;
150 delete pkt;
151 }
152
153
154 MEDIACODEC_API MediaFormatContext_t* InitAvFormatContext(char * file){
155 MediaFormatContext_t* ctx;
156 AVFormatContext *ic;
157 int videoStream=-1;
158 unsigned int i;
159 AVCodecContext *codecCtx;
160
161
162
163
164 ic = avformat_alloc_context();
165 if(avformat_open_input(&ic, file, NULL,NULL)!=0){
166 avformat_free_context(ic);
167 return NULL;
168 }
169
170 if(av_find_stream_info(ic)<0){//查询文件流信息
171 avformat_free_context(ic);
172 return NULL;
173 }
174
175
176 for(i=0; i < ic->nb_streams; i++){
177 if(ic->streams[i]->codec->codec_type== CODEC_TYPE_VIDEO) {
178 videoStream=i;
179 break;
180 }
181 }
182 if( videoStream ==-1){
183 avformat_free_context(ic);
184 return NULL;
185 }
186 codecCtx = ic->streams[videoStream]->codec;
187
188 ctx = new MediaFormatContext_t();
189 ctx->fc = ic;
190 ctx->video.codec_type = (int)CODEC_TYPE_VIDEO;
191 ctx->video.codec_id = (int)codecCtx->codec_id;
192 ctx->video.bitrate = codecCtx->bit_rate;
193 ctx->video.frame_number = codecCtx->frame_number;
194 ctx->video.gopsize = codecCtx->gop_size;
195 ctx->video.height = codecCtx->height;
196 ctx->video.pixfmt = codecCtx->pix_fmt;
197 ctx->video.tb_den = codecCtx->time_base.den;
198 ctx->video.tb_num = codecCtx->time_base.num;
199 ctx->video.width = codecCtx->width;
200 ctx->video.videostream = videoStream;
201 return ctx;
202 }
203
204 MEDIACODEC_API void FreeAvFormatContext(MediaFormatContext_t* ctx){
205 if(ctx->fc){
206 avformat_free_context((AVFormatContext*)ctx->fc);
207 }
208 delete ctx;
209 }
210
211 MEDIACODEC_API MediaPacket_t* ReadNextPacket(MediaFormatContext_t* ctx){
212 MediaPacket_t* pkt = NULL;
213
214 while(1){
215 pkt = AllocPacket();
216 if( pkt == NULL){
217 return NULL;
218 }
219 if( av_read_frame((AVFormatContext*)ctx->fc, (AVPacket*)pkt->pkt) <0 ){
220 FreePacket(pkt,0); //并没有释放内部的packet数据
221 //printf("read_fream error!\n");
222 return NULL;
223 }
224 if( ctx->video.videostream == pkt->pkt->stream_index){
225 pkt->data = pkt->pkt->data;
226 pkt->size = pkt->pkt->size;
227 pkt->stream = pkt->pkt->stream_index;
228 pkt->duration = pkt->pkt->duration;
229 return pkt;
230 }
231 FreePacket(pkt,1);
232 }
233
234 return NULL;
235 }
236
237 MEDIACODEC_API void FlushBuffer(MediaCodecContext_t* codec){
238 avcodec_flush_buffers(codec->codecCtx);
239 }
240
241
242 //跳转到指定时间
243 MEDIACODEC_API int SeekToTime(MediaFormatContext_t* ctx,int timesec){
244 //avcodec_flush_buffers(pFormatCtx->streams[video_stream]->codec);
245
246 if( av_seek_frame(ctx->fc,-1,timesec*AV_TIME_BASE,AVSEEK_FLAG_BACKWARD)<0){
247 return -1;
248 }
249 //avcodec_flush_buffers(ctx->streams[video_stream]->codec);
250 return 0;
251 }
252
253 MEDIACODEC_API void ReadReset(MediaFormatContext_t* ctx){
254 SeekToTime(ctx,0);
255 }
256
python测试代码:
1
2 import ffmpeg
3
4
5 def printStreamInfo(s):
6 print s.codec_type,'codec_id:',s.codec_id,\
7 'width:',s.width,'height:',s.height,\
8 'gopsize:',s.gopsize,'pixfmt:',s.pixfmt,\
9 'timebase_num:',s.tb_num,'timebase_den:',s.tb_den,\
10 'bitrate:',s.bitrate,'framenumber:',s.frame_number,\
11 'streamindex:',s.videostream
12
13 def printFrameInfo(f):
14 print len(f.rgb24[:f.size]),'width:',f.width,'height:',f.height,'duration:',f.duration,'size:',f.size
15
16
17 def saveppm(f,file):
18 fp = open(file,'wb')
19 if not fp:
20 return
21 fp.write( "P6\n%d %d\n255\n"%(f.width,f.height) )
22 fp.write(f.rgb24[:f.size])
23 fp.close()
24 print file
25
26
27 ffmpeg.InitLib()
28 fc = ffmpeg.InitAvFormatContext('d:/movies/marieMcCray.avi')
29 printStreamInfo(fc.contents.video)
30 bytes=0
31 codec = ffmpeg.InitAvCodec(fc.contents.video)
32 print codec #apply a codec
33
34 cnt=0
35 while True:
36 pkt = ffmpeg.ReadNextPacket(fc)
37 if not pkt:break
38 bytes += pkt.contents.size
39 frame = ffmpeg.DecodeVideoFrame(codec,pkt)
40 if frame:
41 cnt+=1
42 printFrameInfo(frame.contents)
43 if cnt < 100:
44 saveppm(frame.contents,"d:/temp4/%s_xx.ppm"%cnt)
45 os.system('convertx %s %s'%("d:/temp4/%s_xx.ppm"%cnt,"d:/temp4/%s_xx.jpg"%cnt))
46 ffmpeg.FreeVideoFrame(frame) #memory leak
47 print pkt.contents.stream,pkt.contents.size,pkt.contents.duration
48 ffmpeg.FreePacket(pkt,1)
49