Chuyển đến nội dung chính

XỬ LÝ HÌNH ẢNH VÀ VIDEO VỚI THƯ VIỆN FFMPEG TRÊN C++.

_ 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ì?.

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 
#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;
}

_ Đây là hàm main và hàm chính của chương trình:

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)
{ 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); }
_ Đây là hàm ghi thành file PPM xuống thư mục của chương trình
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; ydata[0]+y*pFrame->linesize[0], 1, width*3, pFile);

 // Close file
 fclose(pFile);
}
_ Đây là hàm ghi thành file JGP xuống thư mục của chương trình
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

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.