_ Hôm nay lần đầu tiên viết bài trên blog của bạn mình, mình xin chia sẽ với các bạn cách xử lý video trong C++ với thư viên FFMPEG trong bộ openCV. Bài viết dưới đây mình xin hướng dẫn cách lấy 5 frame từ 1 file video.mp4 và lưu dưới dạng JPEG, PPM( dạnh ảnh bitmap full màu) và cách Encode thành 1 file raw theo chuẩn màu YUV420 để truyền đi theo stream qua máy sever Decode ko bị mất màu.
_ Để bắt đầu mình xin nói sơ qua Encode và Decode là gì, bitrate là gì?.
+ Decode là quá trình chuyển đổi các tập tin đa phương tiện từ định dạng không chuẩn sang định dạng chuẩn nhằm phục vụ mục đích xử lý theo thông tin của quá trình encode. trong quá trình decode và encode có 1 thông số quan trọng là codec. Từ CODEC được ghép từ hai từ "Compressor - Decompressor", hoặc phổ biến hơn là "Coder - Decoder". Mình không biết giải thích chính xác nhưng theo mình hiểu thì nó là 1 bộ mã hoá để nén hoặc giải nén video, hình ảnh.
+ Bitrate cái này rất quan trọng, để bạn nén video dung lượng nhỏ nhưng chất lượng thấp hoặc file video chất lượng rât cao và dung lượng cũng rất nặng. Bit là đơn vị cơ bản của dữ liệu mà bạn đã biết ngay từ khi mới tiếp xức với máy tính, bit rates chính là tổng lượng dữ liệu được ghi lại trong mỗi giây, đơn vị của nó là megabit trên giây (1 megabit bằng 1 triệu bit) hay Mbps. Công thức để tính bitrate là:
_ Khai báo biến và hàm init để thiết lập thông số encode để ghi thành file raw:
_ Có gì thắc mắc các bạn cứ phản hồi
_ Để bắt đầu mình xin nói sơ qua Encode và Decode là gì, bitrate là gì?.
Encode và Decode là gì? bitrate là gì?
+ Encode là quá trình chuyển đổi các tập tin đa phương tiện từ định dạng chuẩn sang định dạng không chuẩn nhằm phục vụ mục đích lưu trữ, playback... Hay có thể hiểu nôm na là việc dịch ngôn ngữ. Định dạng chuẩn chứa trong nó nhiều ngôn ngữ khác nhau gọi là codec, bằng cách xử dụng các bộ từ điển (decoder) bạn sẽ biên dịch chúng sang các ngôn ngữ khác "nhẹ hơn, dễ hiểu hơn" bằng các trình encoder.+ Decode là quá trình chuyển đổi các tập tin đa phương tiện từ định dạng không chuẩn sang định dạng chuẩn nhằm phục vụ mục đích xử lý theo thông tin của quá trình encode. trong quá trình decode và encode có 1 thông số quan trọng là codec. Từ CODEC được ghép từ hai từ "Compressor - Decompressor", hoặc phổ biến hơn là "Coder - Decoder". Mình không biết giải thích chính xác nhưng theo mình hiểu thì nó là 1 bộ mã hoá để nén hoặc giải nén video, hình ảnh.
+ Bitrate cái này rất quan trọng, để bạn nén video dung lượng nhỏ nhưng chất lượng thấp hoặc file video chất lượng rât cao và dung lượng cũng rất nặng. Bit là đơn vị cơ bản của dữ liệu mà bạn đã biết ngay từ khi mới tiếp xức với máy tính, bit rates chính là tổng lượng dữ liệu được ghi lại trong mỗi giây, đơn vị của nó là megabit trên giây (1 megabit bằng 1 triệu bit) hay Mbps. Công thức để tính bitrate là:
Bitrate = Width * Height * A * FPS
Width: chiều rộng của video
Height: chiều cao của video
A: Độ sâu của màu
FPS: số frame/s
Bây giờ bắt đầu vào vấn đề chính
_ Để xử dụng thư viện FFMPEG bạn nên cài OpenCV vào Visual Studio để sử dụng. Bạn có thể tham khảo cách cài: tại đây. Hiện tại mình đang dùng thư viện OpenCV2.2_ Khai báo biến và hàm init để thiết lập thông số encode để ghi thành file raw:
#ifdef __cplusplus extern "C" { #endif #include "include/libavcodec/avcodec.h" #include "include/libavformat/avformat.h" #include "include/libswscale/swscale.h" #ifdef __cplusplus } #endif; #include "afxwin.h" #include_ Đây là hàm main và hàm chính của chương trình:#include #include #include #include #include #include using namespace std; #pragma comment(lib, "lib/avformat.lib") #pragma comment(lib, "lib/avcodec.lib") #pragma comment(lib, "lib/avutil.lib") #pragma comment(lib, "lib/swscale.lib") CString m_sPath(_T("c:\\Users\\Admin\\Documents\\Visual Studio 2008\\Projects\\GetFrameMFC\\GetFrameMFC\\New folder")); CString Des_Folder(_T("c:\\usb\\")); AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVPacket packet; AVCodec *pOCodec=NULL; AVCodecContext *pOCodecCtx= NULL; struct SwsContext* dest_img_ctx; int m_encode_fps = 15; int m_encode_kpf = 15; int m_encode_bitperpixel = 25; int m_encode_qcompress = 0.9; int outbuf_size, out_size; uint8_t *outbuf=NULL; unsigned char *b0=NULL,*b1=NULL,*b2=NULL; AVFrame *picture; AVFrame *pic; PixelFormat ImgFmt = PIX_FMT_YUV420P; uint8_t * pixels=NULL; list
Folder; list File; int m_iTotalFolders; int m_iTotalFiles; int init(AVCodecContext* pCodecCtx) { if(pOCodec==NULL || pOCodecCtx==NULL){ pOCodec = avcodec_find_encoder ( CODEC_ID_MPEG4 ); if ( !pOCodec ) { return ( 0 ); } pOCodecCtx = avcodec_alloc_context(); pOCodecCtx->time_base.den=m_encode_fps; pOCodecCtx->time_base.num=1; pOCodecCtx->gop_size = m_encode_kpf; pOCodecCtx->bit_rate =pCodecCtx->width*pCodecCtx->height*m_encode_fps*m_encode_bitperpixel/100; pOCodecCtx->width = pCodecCtx->width; pOCodecCtx->height = pCodecCtx->height; pOCodecCtx->max_b_frames=1; pOCodecCtx->pix_fmt =PIX_FMT_YUV420P; pOCodecCtx->qcompress=m_encode_qcompress; if ( avcodec_open( pOCodecCtx, pOCodec) < 0 ) { return ( 0 ); } } return true; }
int main(int argc, char *argv[]) { const char* filename = "D:\\Elisoft.mp4"; // Register all formats and codecs avcodec_register_all(); avcodec_init(); av_register_all(); av_init_packet(&packet); OpenVideo(filename); return 0; } int OpenVideo(const char* filename)_ Đây là hàm ghi thành file PPM xuống thư mục của chương trình
{ AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVFormatContext *pFormatCtx; int i, videoStream; AVFrame *pFrame; AVFrame *pFrameRGB; SwsContext *pSWSCtx; int frameFinished; int numBytes; uint8_t *buffer; // Open video file pCodecCtx = avcodec_alloc_context(); pFormatCtx = avformat_alloc_context(); if(av_open_input_file(&pFormatCtx, filename, NULL,0, NULL)!=0) return -1; // Couldn't open file // Retrieve stream information if(avformat_find_stream_info(pFormatCtx, NULL)<0) return -1; // Couldn't find stream information // Dump information about file onto standard error av_dump_format(pFormatCtx, 0, filename, 0); // Find the first video stream videoStream=-1; for(i=0; inb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream=i; break; } if(videoStream==-1) return -1; // Didn't find a video stream // Get a pointer to the codec context for the video stream pCodecCtx=pFormatCtx->streams[videoStream]->codec; // Find the decoder for the video stream pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) return -1; // Codec not found // Open codec if(avcodec_open2(pCodecCtx, pCodec, NULL)<0) return -1; // Could not open codec // Allocate video frame pFrame=avcodec_alloc_frame(); // Allocate an AVFrame structure pFrameRGB=avcodec_alloc_frame(); if(pFrameRGB==NULL) return -1; // Determine required buffer size and allocate buffer numBytes=avpicture_get_size(PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); buffer=new uint8_t[numBytes]; // Assign appropriate parts of buffer to image planes in pFrameRGB avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); // Initialize Sws context pSWSCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL); // register for transfer raw outbuf_size = avpicture_get_size(PIX_FMT_BGR24, pCodecCtx->width,pCodecCtx->height); outbuf = (uint8_t *)malloc ( outbuf_size ); if ( outbuf == NULL ) return ( 0 ); memset ( outbuf, 0, outbuf_size ); // Assign appropriate parts of buffer to image planes in pFrameRGB picture= avcodec_alloc_frame(); avpicture_fill((AVPicture *)picture, outbuf, PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height); dest_img_ctx=sws_getContext(pCodecCtx->width,pCodecCtx->height,PIX_FMT_RGB24,pCodecCtx->width,pCodecCtx->height,PIX_FMT_YUV420P,2, NULL, NULL, NULL); init(pCodecCtx); // Read frames and save first five frames to disk try { i=0; int a = 0; while(av_read_frame(pFormatCtx, &packet)>=0) { // Is this a packet from the video stream? if(packet.stream_index==videoStream) { // Decode video frame a=avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); // Did we get a video frame? if(frameFinished) { sws_scale(pSWSCtx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); //Ghi thành file PPM WritePPM(pFrameRGB,pCodecCtx->width, pCodecCtx->height, i); //Ghi thành file JPG WriteJPEG(pCodecCtx,pFrame,i); //Ghi thành file Raw SaveFrame(pCodecCtx,pFrameRGB,pCodecCtx->width,pCodecCtx->height,i); } } // Free the packet that was allocated by av_read_frame av_free_packet(&packet); } }catch (char* t) { } // free sws context sws_freeContext(pSWSCtx); // Free the RGB image delete [] buffer; av_free(pFrameRGB); // Free the YUV frame av_free(pFrame); // Free the picture frame av_free(picture); // Close the codec avcodec_close(pCodecCtx); avcodec_close(pOCodecCtx); // Close the video file av_close_input_file(pFormatCtx); }
void WritePPM(AVFrame *pFrame, int width, int height, int iFrame) { FILE *pFile; char szFilename[32]; int y; // Open file sprintf(szFilename, "frame%d.bmp", iFrame); pFile=fopen(szFilename, "wb"); if(pFile==NULL) return; // Write header fprintf(pFile, "P6\n%d %d\n255\n", width, height); // Write pixel data for(y=0; y_ Đây là hàm ghi thành file JGP xuống thư mục của chương trìnhdata[0]+y*pFrame->linesize[0], 1, width*3, pFile); // Close file fclose(pFile); }
int WriteJPEG (AVCodecContext *pCodecCtx, AVFrame *pFrame, int FrameNo){ AVCodecContext *pOCodecCtx; AVCodec *pOCodec; uint8_t *Buffer; int BufSiz; int BufSizActual; PixelFormat ImgFmt = PIX_FMT_YUVJ420P; //for the newer ffmpeg version, this int to pixelformat FILE *JPEGFile; char JPEGFName[256]; BufSiz = avpicture_get_size ( ImgFmt,pCodecCtx->width,pCodecCtx->height ); Buffer = (uint8_t *)malloc ( BufSiz ); if ( Buffer == NULL ) return ( 0 ); memset ( Buffer, 0, BufSiz ); pOCodecCtx = avcodec_alloc_context ( ); if ( !pOCodecCtx ) { free ( Buffer ); return ( 0 ); } pOCodecCtx->bit_rate = pCodecCtx->bit_rate; pOCodecCtx->width = pCodecCtx->width; pOCodecCtx->height = pCodecCtx->height; pOCodecCtx->pix_fmt = ImgFmt; pOCodecCtx->codec_id = CODEC_ID_MJPEG; pOCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO; pOCodecCtx->time_base.num = pCodecCtx->time_base.num; pOCodecCtx->time_base.den = pCodecCtx->time_base.den; pOCodec = avcodec_find_encoder ( pOCodecCtx->codec_id ); if ( !pOCodec ) { free ( Buffer ); return ( 0 ); } if ( avcodec_open ( pOCodecCtx, pOCodec ) < 0 ) { free ( Buffer ); return ( 0 ); } pOCodecCtx->mb_lmin = pOCodecCtx->lmin = pOCodecCtx->qmin * FF_QP2LAMBDA; pOCodecCtx->mb_lmax = pOCodecCtx->lmax = pOCodecCtx->qmax * FF_QP2LAMBDA; pOCodecCtx->flags = CODEC_FLAG_QSCALE; pOCodecCtx->global_quality = pOCodecCtx->qmin * FF_QP2LAMBDA; pFrame->pts = 1; pFrame->quality = pOCodecCtx->global_quality; BufSizActual = avcodec_encode_video( pOCodecCtx,Buffer,BufSiz,pFrame ); sprintf ( JPEGFName, "%06d.jpg", FrameNo ); JPEGFile = fopen ( JPEGFName, "wb" ); fwrite ( Buffer, 1, BufSizActual, JPEGFile ); fclose ( JPEGFile ); avcodec_close ( pOCodecCtx ); free ( Buffer ); return ( BufSizActual ); }_ Và cuối cùng là hàm encode hình ảnh từ BGR -> YUV420 rồi ghi thành file RAW để truyền lên server. Cái hàm này mình viết cho ứng dụng của mình nên thôi up lên lun cho các bạn tham khảo.
int SaveFrame(AVCodecContext *pCodecCtx,AVFrame* pFrame, int w, int h, int iFrame) { int rt_h=sws_scale(dest_img_ctx, pFrame->data, pFrame->linesize, 0, h , picture->data, picture->linesize); out_size = avcodec_encode_video(pOCodecCtx, outbuf, outbuf_size, picture); if(out_size > 0 ){ CTime time=CTime::GetCurrentTime(); CString sfol,stmp=""; stmp.Format(_T("%s\\%04d%02d%02d_%02d%02d.raw"),m_sPath,time.GetYear(),time.GetMonth(),time.GetDay(),time.GetHour(),time.GetMinute()); CFile wf; bool ok=wf.Open(stmp,CFile::OpenFlags::modeCreate|CFile::OpenFlags::modeWrite|CFile::OpenFlags::modeNoTruncate); int sec=time.GetSecond(); SYSTEMTIME st; GetSystemTime(&st); int keyfr=pOCodecCtx->coded_frame->key_frame; sec=st.wSecond*1000+st.wMilliseconds; if(ok){ wf.SeekToEnd(); int k=wf.GetPosition(); wf.Write(&out_size,4); wf.Write(outbuf,out_size); wf.Write(&sec,4); wf.Write(&k,4); wf.Write(&keyfr,4); wf.Close(); } } //delete[] outbuf; //outbuf = NULL; return 1; }_ Thật ra ở trong hàm OpenVideo()ở trên thì chúng ta đã trích xuất đươc file video sang ảnh YUV420 thông qua câu lệnh này:
// Decode video frame a=avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);_ Nhưng ảnh lúc đó đang bị ngược màu có nghĩa là vật thể màu xanh dương thì hiển thị màu đỏ còn màu đỏ thì hiển thị là màu xanh dương nên các bạn chú ý mình chuyển từ YUV420 -> BRG rồi sau đó trong hàm SaveFrame mình chuyển từ ảnh BGR -> YUV420 với ngữ cảnh là RGB trong biến dest_img_ctx rồi sau đó mới ghi xuống file raw.
_ Có gì thắc mắc các bạn cứ phản hồi
Nhận xét
Đăng nhận xét