Merge branch 'master' of github.com:ZoneMinder/ZoneMinder
This commit is contained in:
commit
47743d6352
|
@ -571,6 +571,23 @@ if(NOT ZM_NO_FFMPEG)
|
||||||
set(optlibsnotfound "${optlibsnotfound} SWScale")
|
set(optlibsnotfound "${optlibsnotfound} SWScale")
|
||||||
endif(SWSCALE_LIBRARIES)
|
endif(SWSCALE_LIBRARIES)
|
||||||
|
|
||||||
|
# rescale (using find_library and find_path)
|
||||||
|
find_library(AVRESAMPLE_LIBRARIES avresample)
|
||||||
|
if(AVRESAMPLE_LIBRARIES)
|
||||||
|
set(HAVE_LIBAVRESAMPLE 1)
|
||||||
|
list(APPEND ZM_BIN_LIBS "${AVRESAMPLE_LIBRARIES}")
|
||||||
|
find_path(AVRESAMPLE_INCLUDE_DIR "libavresample/avresample.h" /usr/include/ffmpeg)
|
||||||
|
if(AVRESAMPLE_INCLUDE_DIR)
|
||||||
|
include_directories("${AVRESAMPLE_INCLUDE_DIR}")
|
||||||
|
set(CMAKE_REQUIRED_INCLUDES "${AVRESAMPLE_INCLUDE_DIR}")
|
||||||
|
endif(AVRESAMPLE_INCLUDE_DIR)
|
||||||
|
mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR)
|
||||||
|
check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H)
|
||||||
|
set(optlibsfound "${optlibsfound} AVResample")
|
||||||
|
else(AVRESAMPLE_LIBRARIES)
|
||||||
|
set(optlibsnotfound "${optlibsnotfound} AVResample")
|
||||||
|
endif(AVRESAMPLE_LIBRARIES)
|
||||||
|
|
||||||
# Find the path to the ffmpeg executable
|
# Find the path to the ffmpeg executable
|
||||||
find_program(FFMPEG_EXECUTABLE
|
find_program(FFMPEG_EXECUTABLE
|
||||||
NAMES ffmpeg avconv
|
NAMES ffmpeg avconv
|
||||||
|
@ -603,6 +620,13 @@ if(NOT ZM_NO_LIBVLC)
|
||||||
endif(LIBVLC_LIBRARIES)
|
endif(LIBVLC_LIBRARIES)
|
||||||
endif(NOT ZM_NO_LIBVLC)
|
endif(NOT ZM_NO_LIBVLC)
|
||||||
|
|
||||||
|
find_package(Boost 1.36.0)
|
||||||
|
if(Boost_FOUND)
|
||||||
|
include_directories(${Boost_INCLUDE_DIRS})
|
||||||
|
set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}")
|
||||||
|
list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}")
|
||||||
|
endif()
|
||||||
|
|
||||||
# *** END OF LIBRARY CHECKS ***
|
# *** END OF LIBRARY CHECKS ***
|
||||||
|
|
||||||
# Check for gnutls or crypto
|
# Check for gnutls or crypto
|
||||||
|
|
19
README.md
19
README.md
|
@ -1,20 +1,7 @@
|
||||||
ZoneMinder H264 Patch
|
ZoneMinder
|
||||||
|
==========
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/ZoneMinder/ZoneMinder.png?branch=feature-h264-videostorage)](https://travis-ci.org/ZoneMinder/ZoneMinder) [![Bountysource](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received)
|
[![Build Status](https://travis-ci.org/ZoneMinder/ZoneMinder.png)](https://travis-ci.org/ZoneMinder/ZoneMinder) [![Bountysource](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received)
|
||||||
|
|
||||||
##Feature-h264-videostorage Branch Details
|
|
||||||
This branch supports direct recording of h264 cameras into MP4 format uisng the h264 Passthrough option, but only with FFMPEG Monitors currently. It also provides h264 encoding for any other monitor type. If you encounter any issues, please open an issue on GitHub and attach it to the h264 milestone. But do remember this is bleeding edge so it will have problems.
|
|
||||||
Thanks to @chriswiggins and @mastertheknife for their work, @SteveGilvarry is now maintaining this branch and welcomes any assistance.
|
|
||||||
|
|
||||||
**The following SQL changes are required, these will be merged to zmupdate once we are ready to merge this branch to master.**
|
|
||||||
```
|
|
||||||
ALTER TABLE `Monitors` ADD `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' AFTER `Deinterlacing` ,
|
|
||||||
ADD `VideoWriter` TINYINT NOT NULL DEFAULT '0' AFTER `SaveJPEGs` ,
|
|
||||||
ADD `EncoderParameters` TEXT NOT NULL AFTER `VideoWriter` ,
|
|
||||||
ADD `RecordAudio` TINYINT NOT NULL DEFAULT '0' AFTER `EncoderParameters` ;
|
|
||||||
|
|
||||||
ALTER TABLE `Events` ADD `DefaultVideo` VARCHAR( 64 ) NOT NULL AFTER `AlarmFrames` ;
|
|
||||||
```
|
|
||||||
|
|
||||||
All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org
|
All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY)
|
configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY)
|
||||||
|
|
||||||
# Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc)
|
# Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc)
|
||||||
set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp)
|
set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp)
|
||||||
|
|
||||||
# A fix for cmake recompiling the source files for every target.
|
# A fix for cmake recompiling the source files for every target.
|
||||||
add_library(zm STATIC ${ZM_BIN_SRC_FILES})
|
add_library(zm STATIC ${ZM_BIN_SRC_FILES})
|
||||||
|
|
|
@ -153,7 +153,7 @@ public:
|
||||||
mHead = mTail = mStorage;
|
mHead = mTail = mStorage;
|
||||||
else if ( level )
|
else if ( level )
|
||||||
{
|
{
|
||||||
if ( (mHead-mStorage) > mSize )
|
if ( ((uintptr_t)mHead-(uintptr_t)mStorage) > mSize )
|
||||||
{
|
{
|
||||||
memcpy( mStorage, mHead, mSize );
|
memcpy( mStorage, mHead, mSize );
|
||||||
mHead = mStorage;
|
mHead = mStorage;
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#include "zm.h"
|
#include "zm.h"
|
||||||
#include "zm_camera.h"
|
#include "zm_camera.h"
|
||||||
|
|
||||||
Camera::Camera( unsigned int p_monitor_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
|
Camera::Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
|
||||||
monitor_id( p_monitor_id ),
|
monitor_id( p_monitor_id ),
|
||||||
type( p_type ),
|
type( p_type ),
|
||||||
width( p_width),
|
width( p_width),
|
||||||
|
@ -55,3 +55,8 @@ Monitor *Camera::getMonitor() {
|
||||||
monitor = Monitor::Load( monitor_id, false, Monitor::QUERY );
|
monitor = Monitor::Load( monitor_id, false, Monitor::QUERY );
|
||||||
return monitor;
|
return monitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Camera::setMonitor( Monitor *p_monitor ) {
|
||||||
|
monitor = p_monitor;
|
||||||
|
monitor_id = monitor->Id();
|
||||||
|
}
|
||||||
|
|
|
@ -47,19 +47,20 @@ protected:
|
||||||
unsigned int subpixelorder;
|
unsigned int subpixelorder;
|
||||||
unsigned int pixels;
|
unsigned int pixels;
|
||||||
unsigned int imagesize;
|
unsigned int imagesize;
|
||||||
int brightness;
|
int brightness;
|
||||||
int hue;
|
int hue;
|
||||||
int colour;
|
int colour;
|
||||||
int contrast;
|
int contrast;
|
||||||
bool capture;
|
bool capture;
|
||||||
bool record_audio;
|
bool record_audio;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Camera( unsigned int p_monitor_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
|
Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
|
||||||
virtual ~Camera();
|
virtual ~Camera();
|
||||||
|
|
||||||
unsigned int getId() const { return( monitor_id ); }
|
unsigned int getId() const { return( monitor_id ); }
|
||||||
Monitor *getMonitor();
|
Monitor *getMonitor();
|
||||||
|
void setMonitor( Monitor *p_monitor );
|
||||||
SourceType Type() const { return( type ); }
|
SourceType Type() const { return( type ); }
|
||||||
bool IsLocal() const { return( type == LOCAL_SRC ); }
|
bool IsLocal() const { return( type == LOCAL_SRC ); }
|
||||||
bool IsRemote() const { return( type == REMOTE_SRC ); }
|
bool IsRemote() const { return( type == REMOTE_SRC ); }
|
||||||
|
@ -80,14 +81,14 @@ public:
|
||||||
virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); }
|
virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); }
|
||||||
|
|
||||||
bool CanCapture() const { return( capture ); }
|
bool CanCapture() const { return( capture ); }
|
||||||
|
|
||||||
bool SupportsNativeVideo() const { return( (type == FFMPEG_SRC )||(type == REMOTE_SRC)); }
|
bool SupportsNativeVideo() const { return( (type == FFMPEG_SRC )||(type == REMOTE_SRC)); }
|
||||||
|
|
||||||
virtual int PrimeCapture() { return( 0 ); }
|
virtual int PrimeCapture() { return( 0 ); }
|
||||||
virtual int PreCapture()=0;
|
virtual int PreCapture()=0;
|
||||||
virtual int Capture( Image &image )=0;
|
virtual int Capture( Image &image )=0;
|
||||||
virtual int PostCapture()=0;
|
virtual int PostCapture()=0;
|
||||||
virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory)=0;
|
virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZM_CAMERA_H
|
#endif // ZM_CAMERA_H
|
||||||
|
|
|
@ -18,8 +18,11 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "zm.h"
|
#include "zm.h"
|
||||||
|
|
||||||
#include "zm_curl_camera.h"
|
#include "zm_curl_camera.h"
|
||||||
|
|
||||||
|
#include "zm_packetqueue.h"
|
||||||
|
|
||||||
#if HAVE_LIBCURL
|
#if HAVE_LIBCURL
|
||||||
|
|
||||||
#define CURL_MAXRETRY 5
|
#define CURL_MAXRETRY 5
|
||||||
|
@ -30,28 +33,24 @@ const char* content_type_match = "Content-Type:";
|
||||||
size_t content_length_match_len;
|
size_t content_length_match_len;
|
||||||
size_t content_type_match_len;
|
size_t content_type_match_len;
|
||||||
|
|
||||||
cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
|
cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
|
||||||
Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
|
Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
|
||||||
mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET )
|
mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET )
|
||||||
{
|
{
|
||||||
|
|
||||||
if ( capture )
|
if ( capture ) {
|
||||||
{
|
|
||||||
Initialise();
|
Initialise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cURLCamera::~cURLCamera()
|
cURLCamera::~cURLCamera() {
|
||||||
{
|
if ( capture ) {
|
||||||
if ( capture )
|
|
||||||
{
|
|
||||||
|
|
||||||
Terminate();
|
Terminate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cURLCamera::Initialise()
|
void cURLCamera::Initialise() {
|
||||||
{
|
|
||||||
content_length_match_len = strlen(content_length_match);
|
content_length_match_len = strlen(content_length_match);
|
||||||
content_type_match_len = strlen(content_type_match);
|
content_type_match_len = strlen(content_type_match);
|
||||||
|
|
||||||
|
@ -88,8 +87,7 @@ void cURLCamera::Initialise()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cURLCamera::Terminate()
|
void cURLCamera::Terminate() {
|
||||||
{
|
|
||||||
/* Signal the thread to terminate */
|
/* Signal the thread to terminate */
|
||||||
bTerminate = true;
|
bTerminate = true;
|
||||||
|
|
||||||
|
@ -108,20 +106,17 @@ void cURLCamera::Terminate()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int cURLCamera::PrimeCapture()
|
int cURLCamera::PrimeCapture() {
|
||||||
{
|
|
||||||
//Info( "Priming capture from %s", mPath.c_str() );
|
//Info( "Priming capture from %s", mPath.c_str() );
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cURLCamera::PreCapture()
|
int cURLCamera::PreCapture() {
|
||||||
{
|
// Nothing to do here
|
||||||
// Nothing to do here
|
return( 0 );
|
||||||
return( 0 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int cURLCamera::Capture( Image &image )
|
int cURLCamera::Capture( Image &image ) {
|
||||||
{
|
|
||||||
bool frameComplete = false;
|
bool frameComplete = false;
|
||||||
|
|
||||||
/* MODE_STREAM specific variables */
|
/* MODE_STREAM specific variables */
|
||||||
|
@ -305,22 +300,19 @@ int cURLCamera::Capture( Image &image )
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cURLCamera::PostCapture()
|
int cURLCamera::PostCapture() {
|
||||||
{
|
|
||||||
// Nothing to do here
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
int cURLCamera::CaptureAndRecord( Image &image, bool recording, char* event_directory )
|
|
||||||
{
|
|
||||||
Error("Capture and Record not implemented for the cURL camera type");
|
|
||||||
// Nothing to do here
|
// Nothing to do here
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int cURLCamera::CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ) {
|
||||||
|
Error("Capture and Record not implemented for the cURL camera type");
|
||||||
|
// Nothing to do here
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata)
|
|
||||||
{
|
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) {
|
||||||
lock();
|
lock();
|
||||||
|
|
||||||
/* Append the data we just received to our buffer */
|
/* Append the data we just received to our buffer */
|
||||||
|
@ -341,8 +333,7 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata)
|
size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) {
|
||||||
{
|
|
||||||
std::string header;
|
std::string header;
|
||||||
header.assign((const char*)buffer, size*nmemb);
|
header.assign((const char*)buffer, size*nmemb);
|
||||||
|
|
||||||
|
@ -382,8 +373,7 @@ size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, voi
|
||||||
return size*nmemb;
|
return size*nmemb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* cURLCamera::thread_func()
|
void* cURLCamera::thread_func() {
|
||||||
{
|
|
||||||
long tRet;
|
long tRet;
|
||||||
double dSize;
|
double dSize;
|
||||||
|
|
||||||
|
@ -529,8 +519,7 @@ int cURLCamera::unlock() {
|
||||||
return nRet;
|
return nRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow)
|
int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) {
|
||||||
{
|
|
||||||
/* Signal the curl thread to terminate */
|
/* Signal the curl thread to terminate */
|
||||||
if(bTerminate)
|
if(bTerminate)
|
||||||
return -10;
|
return -10;
|
||||||
|
@ -539,18 +528,15 @@ int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* These functions call the functions in the class for the correct object */
|
/* These functions call the functions in the class for the correct object */
|
||||||
size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata)
|
size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) {
|
||||||
{
|
|
||||||
return ((cURLCamera*)userdata)->data_callback(buffer,size,nmemb,userdata);
|
return ((cURLCamera*)userdata)->data_callback(buffer,size,nmemb,userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata)
|
size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) {
|
||||||
{
|
|
||||||
return ((cURLCamera*)userdata)->header_callback(buffer,size,nmemb,userdata);
|
return ((cURLCamera*)userdata)->header_callback(buffer,size,nmemb,userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow)
|
int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) {
|
||||||
{
|
|
||||||
return ((cURLCamera*)userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow);
|
return ((cURLCamera*)userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,6 +544,4 @@ void* thread_func_dispatcher(void* object) {
|
||||||
return ((cURLCamera*)object)->thread_func();
|
return ((cURLCamera*)object)->thread_func();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif // HAVE_LIBCURL
|
#endif // HAVE_LIBCURL
|
||||||
|
|
|
@ -39,8 +39,7 @@
|
||||||
// Class representing 'curl' cameras, i.e. those which are
|
// Class representing 'curl' cameras, i.e. those which are
|
||||||
// accessed using the curl library
|
// accessed using the curl library
|
||||||
//
|
//
|
||||||
class cURLCamera : public Camera
|
class cURLCamera : public Camera {
|
||||||
{
|
|
||||||
protected:
|
protected:
|
||||||
typedef enum {MODE_UNSET, MODE_SINGLE, MODE_STREAM} mode_t;
|
typedef enum {MODE_UNSET, MODE_SINGLE, MODE_STREAM} mode_t;
|
||||||
|
|
||||||
|
@ -65,7 +64,7 @@ protected:
|
||||||
pthread_cond_t request_complete_cond;
|
pthread_cond_t request_complete_cond;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
|
cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
|
||||||
~cURLCamera();
|
~cURLCamera();
|
||||||
|
|
||||||
const std::string &Path() const { return( mPath ); }
|
const std::string &Path() const { return( mPath ); }
|
||||||
|
@ -79,7 +78,7 @@ public:
|
||||||
int PreCapture();
|
int PreCapture();
|
||||||
int Capture( Image &image );
|
int Capture( Image &image );
|
||||||
int PostCapture();
|
int PostCapture();
|
||||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory);
|
int CaptureAndRecord( Image &image, struct timeval recording, char* event_directory );
|
||||||
|
|
||||||
size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata);
|
size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata);
|
||||||
size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata);
|
size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata);
|
||||||
|
|
456
src/zm_event.cpp
456
src/zm_event.cpp
File diff suppressed because it is too large
Load Diff
|
@ -47,8 +47,7 @@ class Monitor;
|
||||||
//
|
//
|
||||||
// Class describing events, i.e. captured periods of activity.
|
// Class describing events, i.e. captured periods of activity.
|
||||||
//
|
//
|
||||||
class Event
|
class Event {
|
||||||
{
|
|
||||||
friend class EventStream;
|
friend class EventStream;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -68,8 +67,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
typedef enum { NORMAL, BULK, ALARM } FrameType;
|
typedef enum { NORMAL, BULK, ALARM } FrameType;
|
||||||
|
|
||||||
struct PreAlarmData
|
struct PreAlarmData {
|
||||||
{
|
|
||||||
Image *image;
|
Image *image;
|
||||||
struct timeval timestamp;
|
struct timeval timestamp;
|
||||||
unsigned int score;
|
unsigned int score;
|
||||||
|
@ -103,8 +101,7 @@ protected:
|
||||||
int last_db_frame;
|
int last_db_frame;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void Initialise()
|
static void Initialise() {
|
||||||
{
|
|
||||||
if ( initialised )
|
if ( initialised )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -148,33 +145,26 @@ private:
|
||||||
void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps );
|
void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static const char *getSubPath( struct tm *time )
|
static const char *getSubPath( struct tm *time ) {
|
||||||
{
|
|
||||||
static char subpath[PATH_MAX] = "";
|
static char subpath[PATH_MAX] = "";
|
||||||
snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec );
|
snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec );
|
||||||
return( subpath );
|
return( subpath );
|
||||||
}
|
}
|
||||||
static const char *getSubPath( time_t *time )
|
static const char *getSubPath( time_t *time ) {
|
||||||
{
|
|
||||||
return( Event::getSubPath( localtime( time ) ) );
|
return( Event::getSubPath( localtime( time ) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
char* getEventFile(void)
|
char* getEventFile(void) {
|
||||||
{
|
|
||||||
return video_file;
|
return video_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static int PreAlarmCount()
|
static int PreAlarmCount() {
|
||||||
{
|
|
||||||
return( pre_alarm_count );
|
return( pre_alarm_count );
|
||||||
}
|
}
|
||||||
static void EmptyPreAlarmFrames()
|
static void EmptyPreAlarmFrames() {
|
||||||
{
|
if ( pre_alarm_count > 0 ) {
|
||||||
if ( pre_alarm_count > 0 )
|
for ( int i = 0; i < MAX_PRE_ALARM_FRAMES; i++ ) {
|
||||||
{
|
|
||||||
for ( int i = 0; i < MAX_PRE_ALARM_FRAMES; i++ )
|
|
||||||
{
|
|
||||||
delete pre_alarm_data[i].image;
|
delete pre_alarm_data[i].image;
|
||||||
delete pre_alarm_data[i].alarm_frame;
|
delete pre_alarm_data[i].alarm_frame;
|
||||||
}
|
}
|
||||||
|
@ -182,29 +172,24 @@ public:
|
||||||
}
|
}
|
||||||
pre_alarm_count = 0;
|
pre_alarm_count = 0;
|
||||||
}
|
}
|
||||||
static void AddPreAlarmFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL )
|
static void AddPreAlarmFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ) {
|
||||||
{
|
|
||||||
pre_alarm_data[pre_alarm_count].image = new Image( *image );
|
pre_alarm_data[pre_alarm_count].image = new Image( *image );
|
||||||
pre_alarm_data[pre_alarm_count].timestamp = timestamp;
|
pre_alarm_data[pre_alarm_count].timestamp = timestamp;
|
||||||
pre_alarm_data[pre_alarm_count].score = score;
|
pre_alarm_data[pre_alarm_count].score = score;
|
||||||
if ( alarm_frame )
|
if ( alarm_frame ) {
|
||||||
{
|
|
||||||
pre_alarm_data[pre_alarm_count].alarm_frame = new Image( *alarm_frame );
|
pre_alarm_data[pre_alarm_count].alarm_frame = new Image( *alarm_frame );
|
||||||
}
|
}
|
||||||
pre_alarm_count++;
|
pre_alarm_count++;
|
||||||
}
|
}
|
||||||
void SavePreAlarmFrames()
|
void SavePreAlarmFrames() {
|
||||||
{
|
for ( int i = 0; i < pre_alarm_count; i++ ) {
|
||||||
for ( int i = 0; i < pre_alarm_count; i++ )
|
|
||||||
{
|
|
||||||
AddFrame( pre_alarm_data[i].image, pre_alarm_data[i].timestamp, pre_alarm_data[i].score, pre_alarm_data[i].alarm_frame );
|
AddFrame( pre_alarm_data[i].image, pre_alarm_data[i].timestamp, pre_alarm_data[i].score, pre_alarm_data[i].alarm_frame );
|
||||||
}
|
}
|
||||||
EmptyPreAlarmFrames();
|
EmptyPreAlarmFrames();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class EventStream : public StreamBase
|
class EventStream : public StreamBase {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
|
typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
|
||||||
|
|
||||||
|
@ -217,8 +202,7 @@ protected:
|
||||||
bool in_db;
|
bool in_db;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EventData
|
struct EventData {
|
||||||
{
|
|
||||||
unsigned long event_id;
|
unsigned long event_id;
|
||||||
unsigned long monitor_id;
|
unsigned long monitor_id;
|
||||||
unsigned long frame_count;
|
unsigned long frame_count;
|
||||||
|
@ -254,8 +238,7 @@ protected:
|
||||||
bool sendFrame( int delta_us );
|
bool sendFrame( int delta_us );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EventStream()
|
EventStream() {
|
||||||
{
|
|
||||||
mode = DEFAULT_MODE;
|
mode = DEFAULT_MODE;
|
||||||
|
|
||||||
forceEventChange = false;
|
forceEventChange = false;
|
||||||
|
@ -265,18 +248,15 @@ public:
|
||||||
|
|
||||||
event_data = 0;
|
event_data = 0;
|
||||||
}
|
}
|
||||||
void setStreamStart( int init_event_id, unsigned int init_frame_id=0 )
|
void setStreamStart( int init_event_id, unsigned int init_frame_id=0 ) {
|
||||||
{
|
|
||||||
loadInitialEventData( init_event_id, init_frame_id );
|
loadInitialEventData( init_event_id, init_frame_id );
|
||||||
loadMonitor( event_data->monitor_id );
|
loadMonitor( event_data->monitor_id );
|
||||||
}
|
}
|
||||||
void setStreamStart( int monitor_id, time_t event_time )
|
void setStreamStart( int monitor_id, time_t event_time ) {
|
||||||
{
|
|
||||||
loadInitialEventData( monitor_id, event_time );
|
loadInitialEventData( monitor_id, event_time );
|
||||||
loadMonitor( monitor_id );
|
loadMonitor( monitor_id );
|
||||||
}
|
}
|
||||||
void setStreamMode( StreamMode p_mode )
|
void setStreamMode( StreamMode p_mode ) {
|
||||||
{
|
|
||||||
mode = p_mode;
|
mode = p_mode;
|
||||||
}
|
}
|
||||||
void runStream();
|
void runStream();
|
||||||
|
|
|
@ -41,40 +41,40 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp
|
||||||
|
|
||||||
switch(p_colours) {
|
switch(p_colours) {
|
||||||
case ZM_COLOUR_RGB24:
|
case ZM_COLOUR_RGB24:
|
||||||
{
|
{
|
||||||
if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) {
|
if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) {
|
||||||
/* BGR subpixel order */
|
/* BGR subpixel order */
|
||||||
pf = AV_PIX_FMT_BGR24;
|
pf = AV_PIX_FMT_BGR24;
|
||||||
} else {
|
} else {
|
||||||
/* Assume RGB subpixel order */
|
/* Assume RGB subpixel order */
|
||||||
pf = AV_PIX_FMT_RGB24;
|
pf = AV_PIX_FMT_RGB24;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ZM_COLOUR_RGB32:
|
case ZM_COLOUR_RGB32:
|
||||||
{
|
{
|
||||||
if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
|
if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) {
|
||||||
/* ARGB subpixel order */
|
/* ARGB subpixel order */
|
||||||
pf = AV_PIX_FMT_ARGB;
|
pf = AV_PIX_FMT_ARGB;
|
||||||
} else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
|
} else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) {
|
||||||
/* ABGR subpixel order */
|
/* ABGR subpixel order */
|
||||||
pf = AV_PIX_FMT_ABGR;
|
pf = AV_PIX_FMT_ABGR;
|
||||||
} else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
|
} else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) {
|
||||||
/* BGRA subpixel order */
|
/* BGRA subpixel order */
|
||||||
pf = AV_PIX_FMT_BGRA;
|
pf = AV_PIX_FMT_BGRA;
|
||||||
} else {
|
} else {
|
||||||
/* Assume RGBA subpixel order */
|
/* Assume RGBA subpixel order */
|
||||||
pf = AV_PIX_FMT_RGBA;
|
pf = AV_PIX_FMT_RGBA;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ZM_COLOUR_GRAY8:
|
case ZM_COLOUR_GRAY8:
|
||||||
pf = AV_PIX_FMT_GRAY8;
|
pf = AV_PIX_FMT_GRAY8;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Panic("Unexpected colours: %d",p_colours);
|
Panic("Unexpected colours: %d",p_colours);
|
||||||
pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */
|
pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pf;
|
return pf;
|
||||||
|
@ -158,25 +158,17 @@ SWScale::SWScale() : gotdefaults(false), swscale_ctx(NULL), input_avframe(NULL),
|
||||||
SWScale::~SWScale() {
|
SWScale::~SWScale() {
|
||||||
|
|
||||||
/* Free up everything */
|
/* Free up everything */
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
|
||||||
av_frame_free( &input_avframe );
|
av_frame_free( &input_avframe );
|
||||||
#else
|
|
||||||
av_freep( &input_avframe );
|
|
||||||
#endif
|
|
||||||
//input_avframe = NULL;
|
//input_avframe = NULL;
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
|
||||||
av_frame_free( &output_avframe );
|
av_frame_free( &output_avframe );
|
||||||
#else
|
|
||||||
av_freep( &output_avframe );
|
|
||||||
#endif
|
|
||||||
//output_avframe = NULL;
|
//output_avframe = NULL;
|
||||||
|
|
||||||
if(swscale_ctx) {
|
if(swscale_ctx) {
|
||||||
sws_freeContext(swscale_ctx);
|
sws_freeContext(swscale_ctx);
|
||||||
swscale_ctx = NULL;
|
swscale_ctx = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug(4,"SWScale object destroyed");
|
Debug(4,"SWScale object destroyed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,13 +225,14 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint
|
||||||
#else
|
#else
|
||||||
size_t outsize = avpicture_get_size(out_pf, width, height);
|
size_t outsize = avpicture_get_size(out_pf, width, height);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(outsize < out_buffer_size) {
|
if(outsize < out_buffer_size) {
|
||||||
Error("The output buffer is undersized for the output format. Required: %d Available: %d", outsize, out_buffer_size);
|
Error("The output buffer is undersized for the output format. Required: %d Available: %d", outsize, out_buffer_size);
|
||||||
return -5;
|
return -5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the context */
|
/* Get the context */
|
||||||
swscale_ctx = sws_getCachedContext(swscale_ctx, width, height, in_pf, width, height, out_pf, 0, NULL, NULL, NULL);
|
swscale_ctx = sws_getCachedContext( swscale_ctx, width, height, in_pf, width, height, out_pf, SWS_FAST_BILINEAR, NULL, NULL, NULL );
|
||||||
if(swscale_ctx == NULL) {
|
if(swscale_ctx == NULL) {
|
||||||
Error("Failed getting swscale context");
|
Error("Failed getting swscale context");
|
||||||
return -6;
|
return -6;
|
||||||
|
@ -366,6 +359,22 @@ int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!oformat) {
|
||||||
|
if (format) {
|
||||||
|
oformat = av_guess_format(format, NULL, NULL);
|
||||||
|
if (!oformat) {
|
||||||
|
av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
|
||||||
|
ret = AVERROR(EINVAL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
oformat = av_guess_format(NULL, filename, NULL);
|
||||||
|
if (!oformat) {
|
||||||
|
ret = AVERROR(EINVAL);
|
||||||
|
av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
avformat_free_context(s);
|
avformat_free_context(s);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -387,21 +396,21 @@ int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filename) strncpy(s->filename, filename, sizeof(s->filename));
|
if (filename) strncpy(s->filename, filename, sizeof(s->filename));
|
||||||
*avctx = s;
|
*avctx = s;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zm_log_fps(double d, const char *postfix) {
|
static void zm_log_fps(double d, const char *postfix) {
|
||||||
uint64_t v = lrintf(d * 100);
|
uint64_t v = lrintf(d * 100);
|
||||||
if (!v) {
|
if (!v) {
|
||||||
Debug(3, "%1.4f %s", d, postfix);
|
Debug(1, "%1.4f %s", d, postfix);
|
||||||
} else if (v % 100) {
|
} else if (v % 100) {
|
||||||
Debug(3, "%3.2f %s", d, postfix);
|
Debug(1, "%3.2f %s", d, postfix);
|
||||||
} else if (v % (100 * 1000)) {
|
} else if (v % (100 * 1000)) {
|
||||||
Debug(3, "%1.0f %s", d, postfix);
|
Debug(1, "%1.0f %s", d, postfix);
|
||||||
} else
|
} else
|
||||||
Debug(3, "%1.0fk %s", d / 1000, postfix);
|
Debug(1, "%1.0fk %s", d / 1000, postfix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* "user interface" functions */
|
/* "user interface" functions */
|
||||||
|
@ -413,28 +422,27 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output)
|
||||||
AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
|
AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
|
||||||
|
|
||||||
avcodec_string(buf, sizeof(buf), st->codec, is_output);
|
avcodec_string(buf, sizeof(buf), st->codec, is_output);
|
||||||
Debug(3, " Stream #%d:%d", index, i);
|
Debug(1, " Stream #%d:%d", index, i);
|
||||||
|
|
||||||
/* the pid is an important information, so we display it */
|
/* the pid is an important information, so we display it */
|
||||||
/* XXX: add a generic system */
|
/* XXX: add a generic system */
|
||||||
if (flags & AVFMT_SHOW_IDS)
|
if (flags & AVFMT_SHOW_IDS)
|
||||||
Debug(3, "[0x%x]", st->id);
|
Debug(1, "[0x%x]", st->id);
|
||||||
if (lang)
|
if (lang)
|
||||||
Debug(3, "(%s)", lang->value);
|
Debug(1, "(%s)", lang->value);
|
||||||
av_log(NULL, AV_LOG_DEBUG, ", %d, %d/%d", st->codec_info_nb_frames,
|
Debug(1, ", %d, %d/%d", st->codec_info_nb_frames, st->time_base.num, st->time_base.den);
|
||||||
st->time_base.num, st->time_base.den);
|
Debug(1, ": %s", buf);
|
||||||
Debug(3, ": %s", buf);
|
|
||||||
|
|
||||||
if (st->sample_aspect_ratio.num && // default
|
if (st->sample_aspect_ratio.num && // default
|
||||||
av_cmp_q(st->sample_aspect_ratio, st->codec->sample_aspect_ratio)) {
|
av_cmp_q(st->sample_aspect_ratio, st->codec->sample_aspect_ratio)) {
|
||||||
AVRational display_aspect_ratio;
|
AVRational display_aspect_ratio;
|
||||||
av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den,
|
av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den,
|
||||||
st->codec->width * (int64_t)st->sample_aspect_ratio.num,
|
st->codec->width * (int64_t)st->sample_aspect_ratio.num,
|
||||||
st->codec->height * (int64_t)st->sample_aspect_ratio.den,
|
st->codec->height * (int64_t)st->sample_aspect_ratio.den,
|
||||||
1024 * 1024);
|
1024 * 1024);
|
||||||
Debug(3, ", SAR %d:%d DAR %d:%d",
|
Debug(1, ", SAR %d:%d DAR %d:%d",
|
||||||
st->sample_aspect_ratio.num, st->sample_aspect_ratio.den,
|
st->sample_aspect_ratio.num, st->sample_aspect_ratio.den,
|
||||||
display_aspect_ratio.num, display_aspect_ratio.den);
|
display_aspect_ratio.num, display_aspect_ratio.den);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
|
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||||
|
@ -448,34 +456,56 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output)
|
||||||
if (fps)
|
if (fps)
|
||||||
zm_log_fps(av_q2d(st->avg_frame_rate), tbn || tbc ? "fps, " : "fps");
|
zm_log_fps(av_q2d(st->avg_frame_rate), tbn || tbc ? "fps, " : "fps");
|
||||||
if (tbn)
|
if (tbn)
|
||||||
zm_log_fps(1 / av_q2d(st->time_base), tbc ? "tbn, " : "tbn");
|
zm_log_fps(1 / av_q2d(st->time_base), tbc ? "stream tb numerator , " : "stream tb numerator");
|
||||||
if (tbc)
|
if (tbc)
|
||||||
zm_log_fps(1 / av_q2d(st->codec->time_base), "tbc");
|
zm_log_fps(1 / av_q2d(st->codec->time_base), "codec time base:");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
||||||
Debug(3, " (default)");
|
Debug(1, " (default)");
|
||||||
if (st->disposition & AV_DISPOSITION_DUB)
|
if (st->disposition & AV_DISPOSITION_DUB)
|
||||||
Debug(3, " (dub)");
|
Debug(1, " (dub)");
|
||||||
if (st->disposition & AV_DISPOSITION_ORIGINAL)
|
if (st->disposition & AV_DISPOSITION_ORIGINAL)
|
||||||
Debug(3, " (original)");
|
Debug(1, " (original)");
|
||||||
if (st->disposition & AV_DISPOSITION_COMMENT)
|
if (st->disposition & AV_DISPOSITION_COMMENT)
|
||||||
Debug(3, " (comment)");
|
Debug(1, " (comment)");
|
||||||
if (st->disposition & AV_DISPOSITION_LYRICS)
|
if (st->disposition & AV_DISPOSITION_LYRICS)
|
||||||
Debug(3, " (lyrics)");
|
Debug(1, " (lyrics)");
|
||||||
if (st->disposition & AV_DISPOSITION_KARAOKE)
|
if (st->disposition & AV_DISPOSITION_KARAOKE)
|
||||||
Debug(3, " (karaoke)");
|
Debug(1, " (karaoke)");
|
||||||
if (st->disposition & AV_DISPOSITION_FORCED)
|
if (st->disposition & AV_DISPOSITION_FORCED)
|
||||||
Debug(3, " (forced)");
|
Debug(1, " (forced)");
|
||||||
if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
|
if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
|
||||||
Debug(3, " (hearing impaired)");
|
Debug(1, " (hearing impaired)");
|
||||||
if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED)
|
if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED)
|
||||||
Debug(3, " (visual impaired)");
|
Debug(1, " (visual impaired)");
|
||||||
if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS)
|
if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS)
|
||||||
Debug(3, " (clean effects)");
|
Debug(1, " (clean effects)");
|
||||||
Debug(3, "\n");
|
Debug(1, "\n");
|
||||||
|
|
||||||
//dump_metadata(NULL, st->metadata, " ");
|
//dump_metadata(NULL, st->metadata, " ");
|
||||||
|
|
||||||
//dump_sidedata(NULL, st, " ");
|
//dump_sidedata(NULL, st, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) {
|
||||||
|
const enum AVSampleFormat *p = codec->sample_fmts;
|
||||||
|
|
||||||
|
while (*p != AV_SAMPLE_FMT_NONE) {
|
||||||
|
if (*p == sample_fmt)
|
||||||
|
return 1;
|
||||||
|
else Debug(2, "Not %s", av_get_sample_fmt_name( *p ) );
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
|
||||||
|
#else
|
||||||
|
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) {
|
||||||
|
dst->data = reinterpret_cast<uint8_t*>(new uint64_t[(src->size + FF_INPUT_BUFFER_PADDING_SIZE)/sizeof(uint64_t) + 1]);
|
||||||
|
memcpy(dst->data, src->data, src->size );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
188
src/zm_ffmpeg.h
188
src/zm_ffmpeg.h
|
@ -41,8 +41,8 @@ extern "C" {
|
||||||
* b and c the minor and micro versions of libav
|
* b and c the minor and micro versions of libav
|
||||||
* d and e the minor and micro versions of FFmpeg */
|
* d and e the minor and micro versions of FFmpeg */
|
||||||
#define LIBAVUTIL_VERSION_CHECK(a, b, c, d, e) \
|
#define LIBAVUTIL_VERSION_CHECK(a, b, c, d, e) \
|
||||||
( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||||
(LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
(LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||||
|
|
||||||
#if LIBAVUTIL_VERSION_CHECK(50, 29, 0, 29, 0)
|
#if LIBAVUTIL_VERSION_CHECK(50, 29, 0, 29, 0)
|
||||||
#include <libavutil/opt.h>
|
#include <libavutil/opt.h>
|
||||||
|
@ -59,55 +59,55 @@ extern "C" {
|
||||||
#include <ffmpeg/mathematics.h>
|
#include <ffmpeg/mathematics.h>
|
||||||
#include <ffmpeg/opt.h>
|
#include <ffmpeg/opt.h>
|
||||||
#endif /* HAVE_LIBAVUTIL_AVUTIL_H */
|
#endif /* HAVE_LIBAVUTIL_AVUTIL_H */
|
||||||
|
|
||||||
#if defined(HAVE_LIBAVUTIL_AVUTIL_H)
|
#if defined(HAVE_LIBAVUTIL_AVUTIL_H)
|
||||||
#if LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100)
|
#if LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100)
|
||||||
#define _AVPIXELFORMAT AVPixelFormat
|
#define _AVPIXELFORMAT AVPixelFormat
|
||||||
#else
|
#else
|
||||||
#define _AVPIXELFORMAT PixelFormat
|
#define _AVPIXELFORMAT PixelFormat
|
||||||
#define AV_PIX_FMT_NONE PIX_FMT_NONE
|
#define AV_PIX_FMT_NONE PIX_FMT_NONE
|
||||||
#define AV_PIX_FMT_RGB444 PIX_FMT_RGB444
|
#define AV_PIX_FMT_RGB444 PIX_FMT_RGB444
|
||||||
#define AV_PIX_FMT_RGB555 PIX_FMT_RGB555
|
#define AV_PIX_FMT_RGB555 PIX_FMT_RGB555
|
||||||
#define AV_PIX_FMT_RGB565 PIX_FMT_RGB565
|
#define AV_PIX_FMT_RGB565 PIX_FMT_RGB565
|
||||||
#define AV_PIX_FMT_BGR24 PIX_FMT_BGR24
|
#define AV_PIX_FMT_BGR24 PIX_FMT_BGR24
|
||||||
#define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
|
#define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
|
||||||
#define AV_PIX_FMT_BGRA PIX_FMT_BGRA
|
#define AV_PIX_FMT_BGRA PIX_FMT_BGRA
|
||||||
#define AV_PIX_FMT_ARGB PIX_FMT_ARGB
|
#define AV_PIX_FMT_ARGB PIX_FMT_ARGB
|
||||||
#define AV_PIX_FMT_ABGR PIX_FMT_ABGR
|
#define AV_PIX_FMT_ABGR PIX_FMT_ABGR
|
||||||
#define AV_PIX_FMT_RGBA PIX_FMT_RGBA
|
#define AV_PIX_FMT_RGBA PIX_FMT_RGBA
|
||||||
#define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8
|
#define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8
|
||||||
#define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422
|
#define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422
|
||||||
#define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P
|
#define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P
|
||||||
#define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P
|
#define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P
|
||||||
#define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P
|
#define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P
|
||||||
#define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P
|
#define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P
|
||||||
#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
|
#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
|
||||||
#define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P
|
#define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P
|
||||||
#define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422
|
#define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422
|
||||||
#define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
|
#define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
|
||||||
#define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P
|
#define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P
|
||||||
#define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422
|
#define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422
|
||||||
#define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411
|
#define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411
|
||||||
#define AV_PIX_FMT_BGR565 PIX_FMT_BGR565
|
#define AV_PIX_FMT_BGR565 PIX_FMT_BGR565
|
||||||
#define AV_PIX_FMT_BGR555 PIX_FMT_BGR555
|
#define AV_PIX_FMT_BGR555 PIX_FMT_BGR555
|
||||||
#define AV_PIX_FMT_BGR8 PIX_FMT_BGR8
|
#define AV_PIX_FMT_BGR8 PIX_FMT_BGR8
|
||||||
#define AV_PIX_FMT_BGR4 PIX_FMT_BGR4
|
#define AV_PIX_FMT_BGR4 PIX_FMT_BGR4
|
||||||
#define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE
|
#define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE
|
||||||
#define AV_PIX_FMT_RGB8 PIX_FMT_RGB8
|
#define AV_PIX_FMT_RGB8 PIX_FMT_RGB8
|
||||||
#define AV_PIX_FMT_RGB4 PIX_FMT_RGB4
|
#define AV_PIX_FMT_RGB4 PIX_FMT_RGB4
|
||||||
#define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE
|
#define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE
|
||||||
#define AV_PIX_FMT_NV12 PIX_FMT_NV12
|
#define AV_PIX_FMT_NV12 PIX_FMT_NV12
|
||||||
#define AV_PIX_FMT_NV21 PIX_FMT_NV21
|
#define AV_PIX_FMT_NV21 PIX_FMT_NV21
|
||||||
#define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1
|
#define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1
|
||||||
#define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1
|
#define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1
|
||||||
#define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE
|
#define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE
|
||||||
#define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE
|
#define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE
|
||||||
#define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P
|
#define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P
|
||||||
#define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P
|
#define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P
|
||||||
#define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P
|
#define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P
|
||||||
//#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264
|
//#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264
|
||||||
//#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1
|
//#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1
|
||||||
//#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2
|
//#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2
|
||||||
#endif
|
#endif
|
||||||
#endif /* HAVE_LIBAVUTIL_AVUTIL_H */
|
#endif /* HAVE_LIBAVUTIL_AVUTIL_H */
|
||||||
|
|
||||||
|
@ -122,8 +122,8 @@ extern "C" {
|
||||||
* b and c the minor and micro versions of libav
|
* b and c the minor and micro versions of libav
|
||||||
* d and e the minor and micro versions of FFmpeg */
|
* d and e the minor and micro versions of FFmpeg */
|
||||||
#define LIBAVCODEC_VERSION_CHECK(a, b, c, d, e) \
|
#define LIBAVCODEC_VERSION_CHECK(a, b, c, d, e) \
|
||||||
( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||||
(LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
(LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||||
|
|
||||||
#elif HAVE_FFMPEG_AVCODEC_H
|
#elif HAVE_FFMPEG_AVCODEC_H
|
||||||
#include <ffmpeg/avcodec.h>
|
#include <ffmpeg/avcodec.h>
|
||||||
|
@ -131,9 +131,9 @@ extern "C" {
|
||||||
|
|
||||||
#if defined(HAVE_LIBAVCODEC_AVCODEC_H)
|
#if defined(HAVE_LIBAVCODEC_AVCODEC_H)
|
||||||
#if LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100)
|
#if LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100)
|
||||||
#define _AVCODECID AVCodecID
|
#define _AVCODECID AVCodecID
|
||||||
#else
|
#else
|
||||||
#define _AVCODECID CodecID
|
#define _AVCODECID CodecID
|
||||||
#endif
|
#endif
|
||||||
#endif /* HAVE_LIBAVCODEC_AVCODEC_H */
|
#endif /* HAVE_LIBAVCODEC_AVCODEC_H */
|
||||||
|
|
||||||
|
@ -147,8 +147,8 @@ extern "C" {
|
||||||
* b and c the minor and micro versions of libav
|
* b and c the minor and micro versions of libav
|
||||||
* d and e the minor and micro versions of FFmpeg */
|
* d and e the minor and micro versions of FFmpeg */
|
||||||
#define LIBAVFORMAT_VERSION_CHECK(a, b, c, d, e) \
|
#define LIBAVFORMAT_VERSION_CHECK(a, b, c, d, e) \
|
||||||
( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||||
(LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
(LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||||
|
|
||||||
#elif HAVE_FFMPEG_AVFORMAT_H
|
#elif HAVE_FFMPEG_AVFORMAT_H
|
||||||
#include <ffmpeg/avformat.h>
|
#include <ffmpeg/avformat.h>
|
||||||
|
@ -163,8 +163,8 @@ extern "C" {
|
||||||
* b and c the minor and micro versions of libav
|
* b and c the minor and micro versions of libav
|
||||||
* d and e the minor and micro versions of FFmpeg */
|
* d and e the minor and micro versions of FFmpeg */
|
||||||
#define LIBAVDEVICE_VERSION_CHECK(a, b, c, d, e) \
|
#define LIBAVDEVICE_VERSION_CHECK(a, b, c, d, e) \
|
||||||
( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||||
(LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
(LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||||
|
|
||||||
#elif HAVE_FFMPEG_AVDEVICE_H
|
#elif HAVE_FFMPEG_AVDEVICE_H
|
||||||
#include <ffmpeg/avdevice.h>
|
#include <ffmpeg/avdevice.h>
|
||||||
|
@ -179,8 +179,8 @@ extern "C" {
|
||||||
* b and c the minor and micro versions of libav
|
* b and c the minor and micro versions of libav
|
||||||
* d and e the minor and micro versions of FFmpeg */
|
* d and e the minor and micro versions of FFmpeg */
|
||||||
#define LIBSWSCALE_VERSION_CHECK(a, b, c, d, e) \
|
#define LIBSWSCALE_VERSION_CHECK(a, b, c, d, e) \
|
||||||
( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \
|
||||||
(LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
(LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) )
|
||||||
|
|
||||||
#elif HAVE_FFMPEG_SWSCALE_H
|
#elif HAVE_FFMPEG_SWSCALE_H
|
||||||
#include <ffmpeg/swscale.h>
|
#include <ffmpeg/swscale.h>
|
||||||
|
@ -212,23 +212,23 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp
|
||||||
#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL
|
#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL
|
||||||
class SWScale {
|
class SWScale {
|
||||||
public:
|
public:
|
||||||
SWScale();
|
SWScale();
|
||||||
~SWScale();
|
~SWScale();
|
||||||
int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
||||||
int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size);
|
int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size);
|
||||||
int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size);
|
int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size);
|
||||||
int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
||||||
int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool gotdefaults;
|
bool gotdefaults;
|
||||||
struct SwsContext* swscale_ctx;
|
struct SwsContext* swscale_ctx;
|
||||||
AVFrame* input_avframe;
|
AVFrame* input_avframe;
|
||||||
AVFrame* output_avframe;
|
AVFrame* output_avframe;
|
||||||
enum _AVPIXELFORMAT default_input_pf;
|
enum _AVPIXELFORMAT default_input_pf;
|
||||||
enum _AVPIXELFORMAT default_output_pf;
|
enum _AVPIXELFORMAT default_output_pf;
|
||||||
unsigned int default_width;
|
unsigned int default_width;
|
||||||
unsigned int default_height;
|
unsigned int default_height;
|
||||||
};
|
};
|
||||||
#endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL
|
#endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL
|
||||||
|
|
||||||
|
@ -265,19 +265,19 @@ protected:
|
||||||
*/
|
*/
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
||||||
inline static const std::string av_make_error_string(int errnum)
|
inline static const std::string av_make_error_string(int errnum)
|
||||||
{
|
{
|
||||||
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
||||||
#if LIBAVUTIL_VERSION_CHECK(50, 13, 0, 13, 0)
|
#if LIBAVUTIL_VERSION_CHECK(50, 13, 0, 13, 0)
|
||||||
av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
||||||
#else
|
#else
|
||||||
snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum);
|
snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum);
|
||||||
#endif
|
#endif
|
||||||
return (std::string)errbuf;
|
return (std::string)errbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef av_err2str
|
#undef av_err2str
|
||||||
#define av_err2str(errnum) av_make_error_string(errnum).c_str()
|
#define av_err2str(errnum) av_make_error_string(errnum).c_str()
|
||||||
|
|
||||||
/* The following is copied directly from newer ffmpeg */
|
/* The following is copied directly from newer ffmpeg */
|
||||||
#if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100)
|
#if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100)
|
||||||
|
@ -323,5 +323,29 @@ static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, in
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output);
|
void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output);
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
|
||||||
|
#define zm_av_packet_unref( packet ) av_packet_unref( packet )
|
||||||
|
#define zm_av_packet_ref( dst, src ) av_packet_ref( dst, src )
|
||||||
|
#else
|
||||||
|
#define zm_av_packet_unref( packet ) av_free_packet( packet )
|
||||||
|
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src );
|
||||||
|
#endif
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
|
||||||
|
#define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet )
|
||||||
|
#else
|
||||||
|
#define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet ) avcodec_decode_video( context, rawFrame, frameComplete, packet->data, packet->size)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||||
|
#define zm_av_frame_alloc() av_frame_alloc()
|
||||||
|
#else
|
||||||
|
#define zm_av_frame_alloc() avcodec_alloc_frame()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ! LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
||||||
|
#define av_frame_free( input_avframe ) av_freep( input_avframe )
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt);
|
||||||
|
|
||||||
#endif // ZM_FFMPEG_H
|
#endif // ZM_FFMPEG_H
|
||||||
|
|
|
@ -42,16 +42,17 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri
|
||||||
mMethod( p_method ),
|
mMethod( p_method ),
|
||||||
mOptions( p_options )
|
mOptions( p_options )
|
||||||
{
|
{
|
||||||
if ( capture )
|
if ( capture ) {
|
||||||
{
|
|
||||||
Initialise();
|
Initialise();
|
||||||
}
|
}
|
||||||
|
|
||||||
mFormatContext = NULL;
|
mFormatContext = NULL;
|
||||||
mVideoStreamId = -1;
|
mVideoStreamId = -1;
|
||||||
mAudioStreamId = -1;
|
mAudioStreamId = -1;
|
||||||
mCodecContext = NULL;
|
mVideoCodecContext = NULL;
|
||||||
mCodec = NULL;
|
mAudioCodecContext = NULL;
|
||||||
|
mVideoCodec = NULL;
|
||||||
|
mAudioCodec = NULL;
|
||||||
mRawFrame = NULL;
|
mRawFrame = NULL;
|
||||||
mFrame = NULL;
|
mFrame = NULL;
|
||||||
frameCount = 0;
|
frameCount = 0;
|
||||||
|
@ -60,9 +61,9 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri
|
||||||
mCanCapture = false;
|
mCanCapture = false;
|
||||||
mOpenStart = 0;
|
mOpenStart = 0;
|
||||||
mReopenThread = 0;
|
mReopenThread = 0;
|
||||||
wasRecording = false;
|
|
||||||
videoStore = NULL;
|
videoStore = NULL;
|
||||||
|
video_last_pts = 0;
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
#if HAVE_LIBSWSCALE
|
||||||
mConvertContext = NULL;
|
mConvertContext = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
@ -79,35 +80,35 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri
|
||||||
} else {
|
} else {
|
||||||
Panic("Unexpected colours: %d",colours);
|
Panic("Unexpected colours: %d",colours);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FfmpegCamera::~FfmpegCamera()
|
FfmpegCamera::~FfmpegCamera() {
|
||||||
{
|
|
||||||
|
if ( videoStore ) {
|
||||||
|
delete videoStore;
|
||||||
|
}
|
||||||
CloseFfmpeg();
|
CloseFfmpeg();
|
||||||
|
|
||||||
if ( capture )
|
if ( capture ) {
|
||||||
{
|
|
||||||
Terminate();
|
Terminate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FfmpegCamera::Initialise()
|
void FfmpegCamera::Initialise() {
|
||||||
{
|
|
||||||
if ( logDebugging() )
|
if ( logDebugging() )
|
||||||
av_log_set_level( AV_LOG_DEBUG );
|
av_log_set_level( AV_LOG_DEBUG );
|
||||||
else
|
else
|
||||||
av_log_set_level( AV_LOG_QUIET );
|
av_log_set_level( AV_LOG_QUIET );
|
||||||
|
|
||||||
av_register_all();
|
av_register_all();
|
||||||
|
avformat_network_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FfmpegCamera::Terminate()
|
void FfmpegCamera::Terminate() {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int FfmpegCamera::PrimeCapture()
|
int FfmpegCamera::PrimeCapture() {
|
||||||
{
|
|
||||||
mVideoStreamId = -1;
|
mVideoStreamId = -1;
|
||||||
mAudioStreamId = -1;
|
mAudioStreamId = -1;
|
||||||
Info( "Priming capture from %s", mPath.c_str() );
|
Info( "Priming capture from %s", mPath.c_str() );
|
||||||
|
@ -129,107 +130,101 @@ int FfmpegCamera::Capture( Image &image )
|
||||||
if (!mCanCapture){
|
if (!mCanCapture){
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread.
|
// If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread.
|
||||||
if (mReopenThread != 0) {
|
if (mReopenThread != 0) {
|
||||||
void *retval = 0;
|
void *retval = 0;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = pthread_join(mReopenThread, &retval);
|
ret = pthread_join(mReopenThread, &retval);
|
||||||
if (ret != 0){
|
if (ret != 0){
|
||||||
Error("Could not join reopen thread.");
|
Error("Could not join reopen thread.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Info( "Successfully reopened stream." );
|
Info( "Successfully reopened stream." );
|
||||||
mReopenThread = 0;
|
mReopenThread = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVPacket packet;
|
|
||||||
uint8_t* directbuffer;
|
|
||||||
|
|
||||||
/* Request a writeable buffer of the target image */
|
|
||||||
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
|
|
||||||
if(directbuffer == NULL) {
|
|
||||||
Error("Failed requesting writeable buffer for the captured image.");
|
|
||||||
return (-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int frameComplete = false;
|
int frameComplete = false;
|
||||||
while ( !frameComplete )
|
while ( !frameComplete ) {
|
||||||
{
|
|
||||||
int avResult = av_read_frame( mFormatContext, &packet );
|
int avResult = av_read_frame( mFormatContext, &packet );
|
||||||
if ( avResult < 0 )
|
if ( avResult < 0 ) {
|
||||||
{
|
|
||||||
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
||||||
av_strerror(avResult, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
av_strerror(avResult, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
||||||
if (
|
if (
|
||||||
// Check if EOF.
|
// Check if EOF.
|
||||||
(avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) ||
|
(avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) ||
|
||||||
// Check for Connection failure.
|
// Check for Connection failure.
|
||||||
(avResult == -110)
|
(avResult == -110)
|
||||||
)
|
) {
|
||||||
{
|
Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf );
|
||||||
Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf);
|
|
||||||
ReopenFfmpeg();
|
ReopenFfmpeg();
|
||||||
}
|
}
|
||||||
|
|
||||||
Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, avResult, errbuf );
|
Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, avResult, errbuf );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
Debug( 5, "Got packet from stream %d", packet.stream_index );
|
Debug( 5, "Got packet from stream %d dts (%d) pts(%d)", packet.stream_index, packet.pts, packet.dts );
|
||||||
// What about audio stream? Maybe someday we could do sound detection...
|
// What about audio stream? Maybe someday we could do sound detection...
|
||||||
if ( packet.stream_index == mVideoStreamId )
|
if ( packet.stream_index == mVideoStreamId ) {
|
||||||
{
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
|
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
|
||||||
if ( avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ) < 0 )
|
if (avcodec_decode_video2(mVideoCodecContext, mRawFrame, &frameComplete, &packet) < 0)
|
||||||
#else
|
#else
|
||||||
if ( avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ) < 0 )
|
if (avcodec_decode_video(mVideoCodecContext, mRawFrame, &frameComplete, packet.data, packet.size) < 0)
|
||||||
#endif
|
#endif
|
||||||
Fatal( "Unable to decode frame at frame %d", frameCount );
|
Fatal( "Unable to decode frame at frame %d", frameCount );
|
||||||
|
|
||||||
Debug( 4, "Decoded video packet at frame %d", frameCount );
|
Debug( 4, "Decoded video packet at frame %d", frameCount );
|
||||||
|
|
||||||
if ( frameComplete )
|
if ( frameComplete ) {
|
||||||
{
|
Debug( 4, "Got frame %d", frameCount );
|
||||||
Debug( 3, "Got frame %d", frameCount );
|
|
||||||
|
uint8_t* directbuffer;
|
||||||
|
|
||||||
|
/* Request a writeable buffer of the target image */
|
||||||
|
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
|
||||||
|
if(directbuffer == NULL) {
|
||||||
|
Error("Failed requesting writeable buffer for the captured image.");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||||
av_image_fill_arrays(mFrame->data, mFrame->linesize,
|
av_image_fill_arrays(mFrame->data, mFrame->linesize,
|
||||||
directbuffer, imagePixFormat, width, height, 1);
|
directbuffer, imagePixFormat, width, height, 1);
|
||||||
#else
|
#else
|
||||||
avpicture_fill( (AVPicture *)mFrame, directbuffer,
|
avpicture_fill( (AVPicture *)mFrame, directbuffer,
|
||||||
imagePixFormat, width, height);
|
imagePixFormat, width, height);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
|
||||||
if(mConvertContext == NULL) {
|
|
||||||
mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
|
|
||||||
|
|
||||||
if(mConvertContext == NULL)
|
#if HAVE_LIBSWSCALE
|
||||||
Fatal( "Unable to create conversion context for %s", mPath.c_str() );
|
if(mConvertContext == NULL) {
|
||||||
}
|
mConvertContext = sws_getContext(mVideoCodecContext->width,
|
||||||
|
mVideoCodecContext->height,
|
||||||
if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 )
|
mVideoCodecContext->pix_fmt,
|
||||||
Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount );
|
width, height, imagePixFormat,
|
||||||
|
SWS_BICUBIC, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
if(mConvertContext == NULL)
|
||||||
|
Fatal( "Unable to create conversion context for %s", mPath.c_str() );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0)
|
||||||
|
Fatal("Unable to convert raw format %u to target format %u at frame %d", mVideoCodecContext->pix_fmt, imagePixFormat, frameCount);
|
||||||
#else // HAVE_LIBSWSCALE
|
#else // HAVE_LIBSWSCALE
|
||||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
|
Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
|
||||||
#endif // HAVE_LIBSWSCALE
|
#endif // HAVE_LIBSWSCALE
|
||||||
|
|
||||||
frameCount++;
|
frameCount++;
|
||||||
} // end if frameComplete
|
} // end if frameComplete
|
||||||
} else {
|
} else {
|
||||||
Debug( 4, "Different stream_index %d", packet.stream_index );
|
Debug( 4, "Different stream_index %d", packet.stream_index );
|
||||||
} // end if packet.stream_index == mVideoStreamId
|
} // end if packet.stream_index == mVideoStreamId
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
|
zm_av_packet_unref( &packet );
|
||||||
av_packet_unref( &packet);
|
|
||||||
#else
|
|
||||||
av_free_packet( &packet );
|
|
||||||
#endif
|
|
||||||
} // end while ! frameComplete
|
} // end while ! frameComplete
|
||||||
return (0);
|
return (0);
|
||||||
} // FfmpegCamera::Capture
|
} // FfmpegCamera::Capture
|
||||||
|
|
||||||
int FfmpegCamera::PostCapture()
|
int FfmpegCamera::PostCapture() {
|
||||||
{
|
|
||||||
// Nothing to do here
|
// Nothing to do here
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
@ -291,8 +286,7 @@ int FfmpegCamera::OpenFfmpeg() {
|
||||||
Debug ( 1, "Opened input" );
|
Debug ( 1, "Opened input" );
|
||||||
|
|
||||||
Info( "Stream open %s", mPath.c_str() );
|
Info( "Stream open %s", mPath.c_str() );
|
||||||
startTime=av_gettime();//FIXME here or after find_Stream_info
|
|
||||||
|
|
||||||
//FIXME can speed up initial analysis but need sensible parameters...
|
//FIXME can speed up initial analysis but need sensible parameters...
|
||||||
//mFormatContext->probesize = 32;
|
//mFormatContext->probesize = 32;
|
||||||
//mFormatContext->max_analyze_duration = 32;
|
//mFormatContext->max_analyze_duration = 32;
|
||||||
|
@ -301,24 +295,24 @@ int FfmpegCamera::OpenFfmpeg() {
|
||||||
Debug ( 1, "Calling av_find_stream_info" );
|
Debug ( 1, "Calling av_find_stream_info" );
|
||||||
if ( av_find_stream_info( mFormatContext ) < 0 )
|
if ( av_find_stream_info( mFormatContext ) < 0 )
|
||||||
#else
|
#else
|
||||||
Debug ( 1, "Calling avformat_find_stream_info" );
|
Debug ( 1, "Calling avformat_find_stream_info" );
|
||||||
if ( avformat_find_stream_info( mFormatContext, 0 ) < 0 )
|
if ( avformat_find_stream_info( mFormatContext, 0 ) < 0 )
|
||||||
#endif
|
#endif
|
||||||
Fatal( "Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno) );
|
Fatal( "Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno) );
|
||||||
|
|
||||||
|
startTime = av_gettime();//FIXME here or after find_Stream_info
|
||||||
Debug ( 1, "Got stream info" );
|
Debug ( 1, "Got stream info" );
|
||||||
|
|
||||||
// Find first video stream present
|
// Find first video stream present
|
||||||
|
// The one we want Might not be the first
|
||||||
mVideoStreamId = -1;
|
mVideoStreamId = -1;
|
||||||
mAudioStreamId = -1;
|
mAudioStreamId = -1;
|
||||||
for (unsigned int i=0; i < mFormatContext->nb_streams; i++ )
|
for (unsigned int i=0; i < mFormatContext->nb_streams; i++ ) {
|
||||||
{
|
|
||||||
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
|
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) {
|
||||||
#else
|
#else
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
|
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) {
|
||||||
#endif
|
#endif
|
||||||
{
|
|
||||||
if ( mVideoStreamId == -1 ) {
|
if ( mVideoStreamId == -1 ) {
|
||||||
mVideoStreamId = i;
|
mVideoStreamId = i;
|
||||||
// if we break, then we won't find the audio stream
|
// if we break, then we won't find the audio stream
|
||||||
|
@ -328,59 +322,74 @@ int FfmpegCamera::OpenFfmpeg() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO )
|
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) {
|
||||||
#else
|
#else
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO )
|
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) {
|
||||||
#endif
|
#endif
|
||||||
{
|
|
||||||
if ( mAudioStreamId == -1 ) {
|
if ( mAudioStreamId == -1 ) {
|
||||||
mAudioStreamId = i;
|
mAudioStreamId = i;
|
||||||
} else {
|
} else {
|
||||||
Debug(2, "Have another audio stream." );
|
Debug(2, "Have another audio stream." );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
} // end foreach stream
|
||||||
if ( mVideoStreamId == -1 )
|
if ( mVideoStreamId == -1 )
|
||||||
Fatal( "Unable to locate video stream in %s", mPath.c_str() );
|
Fatal( "Unable to locate video stream in %s", mPath.c_str() );
|
||||||
if ( mAudioStreamId == -1 )
|
if ( mAudioStreamId == -1 )
|
||||||
Debug( 3, "Unable to locate audio stream in %s", mPath.c_str() );
|
Debug( 3, "Unable to locate audio stream in %s", mPath.c_str() );
|
||||||
|
|
||||||
Debug ( 1, "Found video stream" );
|
Debug ( 3, "Found video stream at index %d", mVideoStreamId );
|
||||||
|
Debug ( 3, "Found audio stream at index %d", mAudioStreamId );
|
||||||
|
|
||||||
mCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
||||||
|
// STolen from ispy
|
||||||
|
//this fixes issues with rtsp streams!! woot.
|
||||||
|
//mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG2_CHUNKS | CODEC_FLAG_LOW_DELAY; // Enable faster H264 decode.
|
||||||
|
mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY;
|
||||||
|
|
||||||
// Try and get the codec from the codec context
|
// Try and get the codec from the codec context
|
||||||
if ( (mCodec = avcodec_find_decoder( mCodecContext->codec_id )) == NULL )
|
if ((mVideoCodec = avcodec_find_decoder(mVideoCodecContext->codec_id)) == NULL) {
|
||||||
Fatal( "Can't find codec for video stream from %s", mPath.c_str() );
|
Fatal("Can't find codec for video stream from %s", mPath.c_str());
|
||||||
|
} else {
|
||||||
Debug ( 1, "Found decoder" );
|
Debug(1, "Video Found decoder");
|
||||||
|
zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0);
|
||||||
// Open the codec
|
// Open the codec
|
||||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
|
||||||
Debug ( 1, "Calling avcodec_open" );
|
Debug ( 1, "Calling avcodec_open" );
|
||||||
if ( avcodec_open( mCodecContext, mCodec ) < 0 )
|
if (avcodec_open(mVideoCodecContext, mVideoCodec) < 0)
|
||||||
#else
|
#else
|
||||||
Debug ( 1, "Calling avcodec_open2" );
|
Debug ( 1, "Calling avcodec_open2" );
|
||||||
if ( avcodec_open2( mCodecContext, mCodec, 0 ) < 0 )
|
if (avcodec_open2(mVideoCodecContext, mVideoCodec, 0) < 0)
|
||||||
#endif
|
#endif
|
||||||
Fatal( "Unable to open codec for video stream from %s", mPath.c_str() );
|
Fatal( "Unable to open codec for video stream from %s", mPath.c_str() );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mAudioStreamId >= 0) {
|
||||||
|
mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec;
|
||||||
|
if ((mAudioCodec = avcodec_find_decoder(mAudioCodecContext->codec_id)) == NULL) {
|
||||||
|
Debug(1, "Can't find codec for audio stream from %s", mPath.c_str());
|
||||||
|
} else {
|
||||||
|
Debug(1, "Audio Found decoder");
|
||||||
|
zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0);
|
||||||
|
// Open the codec
|
||||||
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
|
||||||
|
Debug ( 1, "Calling avcodec_open" );
|
||||||
|
if (avcodec_open(mAudioCodecContext, mAudioCodec) < 0)
|
||||||
|
#else
|
||||||
|
Debug ( 1, "Calling avcodec_open2" );
|
||||||
|
if (avcodec_open2(mAudioCodecContext, mAudioCodec, 0) < 0)
|
||||||
|
#endif
|
||||||
|
Fatal( "Unable to open codec for video stream from %s", mPath.c_str() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Debug ( 1, "Opened codec" );
|
Debug ( 1, "Opened codec" );
|
||||||
|
|
||||||
// Allocate space for the native video frame
|
// Allocate space for the native video frame
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
mRawFrame = zm_av_frame_alloc();
|
||||||
mRawFrame = av_frame_alloc();
|
|
||||||
#else
|
|
||||||
mRawFrame = avcodec_alloc_frame();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Allocate space for the converted video frame
|
// Allocate space for the converted video frame
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
mFrame = zm_av_frame_alloc();
|
||||||
mFrame = av_frame_alloc();
|
|
||||||
#else
|
|
||||||
mFrame = avcodec_alloc_frame();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(mRawFrame == NULL || mFrame == NULL)
|
if(mRawFrame == NULL || mFrame == NULL)
|
||||||
Fatal( "Unable to allocate frame for %s", mPath.c_str() );
|
Fatal( "Unable to allocate frame for %s", mPath.c_str() );
|
||||||
|
@ -398,21 +407,33 @@ int FfmpegCamera::OpenFfmpeg() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug ( 1, "Validated imagesize" );
|
Debug ( 1, "Validated imagesize" );
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
#if HAVE_LIBSWSCALE
|
||||||
Debug ( 1, "Calling sws_isSupportedInput" );
|
Debug ( 1, "Calling sws_isSupportedInput" );
|
||||||
if(!sws_isSupportedInput(mCodecContext->pix_fmt)) {
|
if (!sws_isSupportedInput(mVideoCodecContext->pix_fmt)) {
|
||||||
Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff));
|
Fatal("swscale does not support the codec format: %c%c%c%c", (mVideoCodecContext->pix_fmt)&0xff, ((mVideoCodecContext->pix_fmt >> 8)&0xff), ((mVideoCodecContext->pix_fmt >> 16)&0xff), ((mVideoCodecContext->pix_fmt >> 24)&0xff));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!sws_isSupportedOutput(imagePixFormat)) {
|
if(!sws_isSupportedOutput(imagePixFormat)) {
|
||||||
Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff));
|
Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mConvertContext = sws_getContext(mVideoCodecContext->width,
|
||||||
|
mVideoCodecContext->height,
|
||||||
|
mVideoCodecContext->pix_fmt,
|
||||||
|
width, height,
|
||||||
|
imagePixFormat, SWS_BICUBIC, NULL,
|
||||||
|
NULL, NULL);
|
||||||
|
if ( mConvertContext == NULL )
|
||||||
|
Fatal( "Unable to create conversion context for %s", mPath.c_str() );
|
||||||
#else // HAVE_LIBSWSCALE
|
#else // HAVE_LIBSWSCALE
|
||||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
|
Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
|
||||||
#endif // HAVE_LIBSWSCALE
|
#endif // HAVE_LIBSWSCALE
|
||||||
|
|
||||||
|
if ( (unsigned int)mVideoCodecContext->width != width || (unsigned int)mVideoCodecContext->height != height ) {
|
||||||
|
Warning( "Monitor dimensions are %dx%d but camera is sending %dx%d", width, height, mVideoCodecContext->width, mVideoCodecContext->height );
|
||||||
|
}
|
||||||
|
|
||||||
mCanCapture = true;
|
mCanCapture = true;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -437,14 +458,9 @@ int FfmpegCamera::CloseFfmpeg(){
|
||||||
|
|
||||||
mCanCapture = false;
|
mCanCapture = false;
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
|
||||||
av_frame_free( &mFrame );
|
av_frame_free( &mFrame );
|
||||||
av_frame_free( &mRawFrame );
|
av_frame_free( &mRawFrame );
|
||||||
#else
|
|
||||||
av_freep( &mFrame );
|
|
||||||
av_freep( &mRawFrame );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
#if HAVE_LIBSWSCALE
|
||||||
if ( mConvertContext )
|
if ( mConvertContext )
|
||||||
{
|
{
|
||||||
|
@ -453,13 +469,16 @@ int FfmpegCamera::CloseFfmpeg(){
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ( mCodecContext )
|
if (mVideoCodecContext) {
|
||||||
{
|
avcodec_close(mVideoCodecContext);
|
||||||
avcodec_close( mCodecContext );
|
mVideoCodecContext = NULL; // Freed by av_close_input_file
|
||||||
mCodecContext = NULL; // Freed by av_close_input_file
|
|
||||||
}
|
}
|
||||||
if ( mFormatContext )
|
if (mAudioCodecContext) {
|
||||||
{
|
avcodec_close(mAudioCodecContext);
|
||||||
|
mAudioCodecContext = NULL; // Freed by av_close_input_file
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mFormatContext ) {
|
||||||
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
|
#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0)
|
||||||
av_close_input_file( mFormatContext );
|
av_close_input_file( mFormatContext );
|
||||||
#else
|
#else
|
||||||
|
@ -509,211 +528,274 @@ void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){
|
||||||
}
|
}
|
||||||
|
|
||||||
//Function to handle capture and store
|
//Function to handle capture and store
|
||||||
int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_file ){
|
int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event_file ) {
|
||||||
if (!mCanCapture){
|
if (!mCanCapture){
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
int ret;
|
||||||
|
static char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
||||||
|
|
||||||
// If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread.
|
// If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread.
|
||||||
if (mReopenThread != 0) {
|
if (mReopenThread != 0) {
|
||||||
void *retval = 0;
|
void *retval = 0;
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = pthread_join(mReopenThread, &retval);
|
ret = pthread_join(mReopenThread, &retval);
|
||||||
if (ret != 0){
|
if (ret != 0){
|
||||||
Error("Could not join reopen thread.");
|
Error("Could not join reopen thread.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Info( "Successfully reopened stream." );
|
Info( "Successfully reopened stream." );
|
||||||
mReopenThread = 0;
|
mReopenThread = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVPacket packet;
|
if (mVideoCodecContext->codec_id != AV_CODEC_ID_H264) {
|
||||||
uint8_t* directbuffer;
|
|
||||||
|
|
||||||
/* Request a writeable buffer of the target image */
|
|
||||||
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
|
|
||||||
if( directbuffer == NULL ) {
|
|
||||||
Error("Failed requesting writeable buffer for the captured image.");
|
|
||||||
return (-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( mCodecContext->codec_id != AV_CODEC_ID_H264 ) {
|
|
||||||
Error( "Input stream is not h264. The stored event file may not be viewable in browser." );
|
Error( "Input stream is not h264. The stored event file may not be viewable in browser." );
|
||||||
}
|
}
|
||||||
|
|
||||||
int frameComplete = false;
|
int frameComplete = false;
|
||||||
while ( !frameComplete ) {
|
while ( ! frameComplete ) {
|
||||||
int avResult = av_read_frame( mFormatContext, &packet );
|
av_init_packet( &packet );
|
||||||
if ( avResult < 0 ) {
|
|
||||||
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
ret = av_read_frame( mFormatContext, &packet );
|
||||||
av_strerror(avResult, errbuf, AV_ERROR_MAX_STRING_SIZE);
|
if ( ret < 0 ) {
|
||||||
|
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
|
||||||
if (
|
if (
|
||||||
// Check if EOF.
|
// Check if EOF.
|
||||||
(avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) ||
|
(ret == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) ||
|
||||||
// Check for Connection failure.
|
// Check for Connection failure.
|
||||||
(avResult == -110)
|
(ret == -110)
|
||||||
) {
|
) {
|
||||||
Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf);
|
Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf);
|
||||||
ReopenFfmpeg();
|
ReopenFfmpeg();
|
||||||
}
|
}
|
||||||
|
|
||||||
Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, avResult, errbuf );
|
Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
Debug( 5, "Got packet from stream %d", packet.stream_index );
|
|
||||||
if ( packet.stream_index == mVideoStreamId ) {
|
int key_frame = packet.flags & AV_PKT_FLAG_KEY;
|
||||||
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
|
|
||||||
if ( avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ) < 0 )
|
Debug( 4, "Got packet from stream %d packet pts (%d) dts(%d), key?(%d)",
|
||||||
#else
|
packet.stream_index, packet.pts, packet.dts,
|
||||||
if ( avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ) < 0 )
|
key_frame
|
||||||
|
);
|
||||||
|
|
||||||
|
//Video recording
|
||||||
|
if ( recording.tv_sec ) {
|
||||||
|
// The directory we are recording to is no longer tied to the current event.
|
||||||
|
// Need to re-init the videostore with the correct directory and start recording again
|
||||||
|
// for efficiency's sake, we should test for keyframe before we test for directory change...
|
||||||
|
if ( videoStore && key_frame && (strcmp(oldDirectory, event_file) != 0 ) ) {
|
||||||
|
// don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...
|
||||||
|
// if we store our key frame location with the event will that be enough?
|
||||||
|
Info("Re-starting video storage module");
|
||||||
|
|
||||||
|
// I don't know if this is important or not... but I figure we might as well write this last packet out to the store before closing it.
|
||||||
|
// Also don't know how much it matters for audio.
|
||||||
|
if ( packet.stream_index == mVideoStreamId ) {
|
||||||
|
//Write the packet to our video store
|
||||||
|
int ret = videoStore->writeVideoFramePacket( &packet );
|
||||||
|
if ( ret < 0 ) { //Less than zero and we skipped a frame
|
||||||
|
Warning("Error writing last packet to videostore.");
|
||||||
|
}
|
||||||
|
} // end if video
|
||||||
|
|
||||||
|
delete videoStore;
|
||||||
|
videoStore = NULL;
|
||||||
|
} // end if end of recording
|
||||||
|
|
||||||
|
if ( ( ! videoStore ) && key_frame && ( packet.stream_index == mVideoStreamId ) ) {
|
||||||
|
//Instantiate the video storage module
|
||||||
|
|
||||||
|
if (record_audio) {
|
||||||
|
if (mAudioStreamId == -1) {
|
||||||
|
Debug(3, "Record Audio on but no audio stream found");
|
||||||
|
videoStore = new VideoStore((const char *) event_file, "mp4",
|
||||||
|
mFormatContext->streams[mVideoStreamId],
|
||||||
|
NULL,
|
||||||
|
startTime,
|
||||||
|
this->getMonitor());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Debug(3, "Video module initiated with audio stream");
|
||||||
|
videoStore = new VideoStore((const char *) event_file, "mp4",
|
||||||
|
mFormatContext->streams[mVideoStreamId],
|
||||||
|
mFormatContext->streams[mAudioStreamId],
|
||||||
|
startTime,
|
||||||
|
this->getMonitor());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Debug(3, "Record_audio is false so exclude audio stream");
|
||||||
|
videoStore = new VideoStore((const char *) event_file, "mp4",
|
||||||
|
mFormatContext->streams[mVideoStreamId],
|
||||||
|
NULL,
|
||||||
|
startTime,
|
||||||
|
this->getMonitor());
|
||||||
|
} // end if record_audio
|
||||||
|
strcpy(oldDirectory, event_file);
|
||||||
|
|
||||||
|
// Need to write out all the frames from the last keyframe?
|
||||||
|
// No... need to write out all frames from when the event began. Due to PreEventFrames, this could be more than since the last keyframe.
|
||||||
|
unsigned int packet_count = 0;
|
||||||
|
ZMPacket *queued_packet;
|
||||||
|
|
||||||
|
packetqueue.clear_unwanted_packets( &recording, mVideoStreamId );
|
||||||
|
|
||||||
|
while ( ( queued_packet = packetqueue.popPacket() ) ) {
|
||||||
|
AVPacket *avp = queued_packet->av_packet();
|
||||||
|
|
||||||
|
packet_count += 1;
|
||||||
|
//Write the packet to our video store
|
||||||
|
Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue.size() );
|
||||||
|
if ( avp->stream_index == mVideoStreamId ) {
|
||||||
|
ret = videoStore->writeVideoFramePacket( avp );
|
||||||
|
} else if ( avp->stream_index == mAudioStreamId ) {
|
||||||
|
ret = videoStore->writeAudioFramePacket( avp );
|
||||||
|
} else {
|
||||||
|
Warning("Unknown stream id in queued packet (%d)", avp->stream_index );
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
//Less than zero and we skipped a frame
|
||||||
|
}
|
||||||
|
delete queued_packet;
|
||||||
|
} // end while packets in the packetqueue
|
||||||
|
Debug(2, "Wrote %d queued packets", packet_count );
|
||||||
|
} // end if ! was recording
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Not recording
|
||||||
|
if ( videoStore ) {
|
||||||
|
Info("Deleting videoStore instance");
|
||||||
|
delete videoStore;
|
||||||
|
videoStore = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer video packets, since we are not recording.
|
||||||
|
// All audio packets are keyframes, so only if it's a video keyframe
|
||||||
|
if ( packet.stream_index == mVideoStreamId) {
|
||||||
|
if ( key_frame ) {
|
||||||
|
Debug(3, "Clearing queue");
|
||||||
|
packetqueue.clearQueue( monitor->GetPreEventCount(), mVideoStreamId );
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
// Not sure this is valid. While a camera will PROBABLY always have an increasing pts... it doesn't have to.
|
||||||
|
// Also, I think there are integer wrap-around issues.
|
||||||
|
|
||||||
|
else if ( packet.pts && video_last_pts > packet.pts ) {
|
||||||
|
Warning( "Clearing queue due to out of order pts packet.pts(%d) < video_last_pts(%d)");
|
||||||
|
packetqueue.clearQueue();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
{
|
}
|
||||||
Error( "Unable to decode frame at frame %d, continuing...", frameCount );
|
|
||||||
av_free_packet( &packet );
|
if (
|
||||||
|
( packet.stream_index != mAudioStreamId || record_audio )
|
||||||
|
&&
|
||||||
|
( key_frame || packetqueue.size() )
|
||||||
|
) {
|
||||||
|
packetqueue.queuePacket( &packet );
|
||||||
|
}
|
||||||
|
} // end if recording or not
|
||||||
|
|
||||||
|
if ( packet.stream_index == mVideoStreamId ) {
|
||||||
|
if ( videoStore ) {
|
||||||
|
//Write the packet to our video store
|
||||||
|
int ret = videoStore->writeVideoFramePacket( &packet );
|
||||||
|
if ( ret < 0 ) { //Less than zero and we skipped a frame
|
||||||
|
zm_av_packet_unref( &packet );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug(4, "about to decode video" );
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0)
|
||||||
|
ret = avcodec_send_packet( mVideoCodecContext, &packet );
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
|
||||||
|
Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf );
|
||||||
|
zm_av_packet_unref( &packet );
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
ret = avcodec_receive_frame( mVideoCodecContext, mRawFrame );
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
|
||||||
|
Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf );
|
||||||
|
zm_av_packet_unref( &packet );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
frameComplete = 1;
|
||||||
|
# else
|
||||||
|
ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet );
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
|
||||||
|
Error( "Unable to decode frame at frame %d: %s, continuing", frameCount, errbuf );
|
||||||
|
zm_av_packet_unref( &packet );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Debug( 4, "Decoded video packet at frame %d", frameCount );
|
Debug( 4, "Decoded video packet at frame %d", frameCount );
|
||||||
|
|
||||||
if ( frameComplete ) {
|
if ( frameComplete ) {
|
||||||
Debug( 3, "Got frame %d", frameCount );
|
Debug( 4, "Got frame %d", frameCount );
|
||||||
|
|
||||||
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
|
|
||||||
|
|
||||||
//Keep the last keyframe so we can establish immediate video
|
uint8_t* directbuffer;
|
||||||
/*if(packet.flags & AV_PKT_FLAG_KEY)
|
|
||||||
av_copy_packet(&lastKeyframePkt, &packet);*/
|
|
||||||
//TODO I think we need to store the key frame location for seeking as part of the event
|
|
||||||
|
|
||||||
//Video recording
|
|
||||||
if ( recording && !wasRecording ) {
|
|
||||||
//Instantiate the video storage module
|
|
||||||
|
|
||||||
if (record_audio) {
|
/* Request a writeable buffer of the target image */
|
||||||
if (mAudioStreamId == -1) {
|
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
|
||||||
Debug(3, "Record Audio on but no audio stream found");
|
if ( directbuffer == NULL ) {
|
||||||
videoStore = new VideoStore((const char *) event_file, "mp4",
|
Error("Failed requesting writeable buffer for the captured image.");
|
||||||
mFormatContext->streams[mVideoStreamId],
|
zm_av_packet_unref( &packet );
|
||||||
NULL,
|
return (-1);
|
||||||
startTime,
|
|
||||||
this->getMonitor()->getOrientation());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Debug(3, "Video module initiated with audio stream");
|
|
||||||
videoStore = new VideoStore((const char *) event_file, "mp4",
|
|
||||||
mFormatContext->streams[mVideoStreamId],
|
|
||||||
mFormatContext->streams[mAudioStreamId],
|
|
||||||
startTime,
|
|
||||||
this->getMonitor()->getOrientation());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Debug(3, "Record_audio is false so exclude audio stream");
|
|
||||||
videoStore = new VideoStore((const char *) event_file, "mp4",
|
|
||||||
mFormatContext->streams[mVideoStreamId],
|
|
||||||
NULL,
|
|
||||||
startTime,
|
|
||||||
this->getMonitor()->getOrientation());
|
|
||||||
}
|
|
||||||
wasRecording = true;
|
|
||||||
strcpy(oldDirectory, event_file);
|
|
||||||
|
|
||||||
} else if ( ( ! recording ) && wasRecording && videoStore ) {
|
|
||||||
Info("Deleting videoStore instance");
|
|
||||||
delete videoStore;
|
|
||||||
videoStore = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The directory we are recording to is no longer tied to the current
|
|
||||||
// event. Need to re-init the videostore with the correct directory and
|
|
||||||
// start recording again
|
|
||||||
if (recording && wasRecording && (strcmp(oldDirectory, event_file) != 0)
|
|
||||||
&& (packet.flags & AV_PKT_FLAG_KEY)) {
|
|
||||||
// Don't open new videostore until we're on a key frame..would this
|
|
||||||
// require an offset adjustment for the event as a result?...if we store
|
|
||||||
// our key frame location with the event will that be enough?
|
|
||||||
Info("Re-starting video storage module");
|
|
||||||
if(videoStore){
|
|
||||||
delete videoStore;
|
|
||||||
videoStore = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (record_audio) {
|
|
||||||
if (mAudioStreamId == -1) {
|
|
||||||
Debug(3, "Record Audio on but no audio stream found");
|
|
||||||
videoStore = new VideoStore((const char *) event_file, "mp4",
|
|
||||||
mFormatContext->streams[mVideoStreamId],
|
|
||||||
NULL,
|
|
||||||
startTime,
|
|
||||||
this->getMonitor()->getOrientation());
|
|
||||||
} else {
|
|
||||||
Debug(3, "Video module initiated with audio stream");
|
|
||||||
videoStore = new VideoStore((const char *) event_file, "mp4",
|
|
||||||
mFormatContext->streams[mVideoStreamId],
|
|
||||||
mFormatContext->streams[mAudioStreamId],
|
|
||||||
startTime,
|
|
||||||
this->getMonitor()->getOrientation());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Debug(3, "Record_audio is false so exclude audio stream");
|
|
||||||
videoStore = new VideoStore((const char *) event_file, "mp4",
|
|
||||||
mFormatContext->streams[mVideoStreamId],
|
|
||||||
NULL, startTime,
|
|
||||||
this->getMonitor()->getOrientation());
|
|
||||||
}
|
|
||||||
strcpy(oldDirectory, event_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( videoStore && recording ) {
|
|
||||||
//Write the packet to our video store
|
|
||||||
int ret = videoStore->writeVideoFramePacket(&packet,
|
|
||||||
mFormatContext->streams[mVideoStreamId]); //, &lastKeyframePkt);
|
|
||||||
if(ret<0){//Less than zero and we skipped a frame
|
|
||||||
av_free_packet( &packet );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
|
||||||
if ( mConvertContext == NULL ) {
|
|
||||||
mConvertContext = sws_getContext(mCodecContext->width,
|
|
||||||
mCodecContext->height,
|
|
||||||
mCodecContext->pix_fmt,
|
|
||||||
width, height,
|
|
||||||
imagePixFormat, SWS_BICUBIC, NULL,
|
|
||||||
NULL, NULL);
|
|
||||||
if ( mConvertContext == NULL )
|
|
||||||
Fatal( "Unable to create conversion context for %s", mPath.c_str() );
|
|
||||||
}
|
}
|
||||||
|
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||||
|
av_image_fill_arrays(mFrame->data, mFrame->linesize, directbuffer, imagePixFormat, width, height, 1);
|
||||||
|
#else
|
||||||
|
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize,
|
if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize,
|
||||||
0, mCodecContext->height, mFrame->data, mFrame->linesize) < 0)
|
0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) {
|
||||||
Fatal("Unable to convert raw format %u to target format %u at frame %d",
|
Fatal("Unable to convert raw format %u to target format %u at frame %d",
|
||||||
mCodecContext->pix_fmt, imagePixFormat, frameCount);
|
mVideoCodecContext->pix_fmt, imagePixFormat, frameCount);
|
||||||
#else // HAVE_LIBSWSCALE
|
}
|
||||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" );
|
|
||||||
#endif // HAVE_LIBSWSCALE
|
|
||||||
|
|
||||||
frameCount++;
|
frameCount++;
|
||||||
} // end if frameComplete
|
} else {
|
||||||
} else if ( packet.stream_index == mAudioStreamId ) { //FIXME best way to copy all other streams
|
Debug( 3, "Not framecomplete after av_read_frame" );
|
||||||
if ( videoStore && recording ) {
|
} // end if frameComplete
|
||||||
if ( record_audio ) {
|
} else if ( packet.stream_index == mAudioStreamId ) { //FIXME best way to copy all other streams
|
||||||
Debug(4, "Recording audio packet" );
|
if ( videoStore ) {
|
||||||
//Write the packet to our video store
|
if ( record_audio ) {
|
||||||
int ret = videoStore->writeAudioFramePacket(&packet,
|
Debug(3, "Recording audio packet streamindex(%d) packetstreamindex(%d)", mAudioStreamId, packet.stream_index );
|
||||||
mFormatContext->streams[packet.stream_index]); //FIXME no relevance of last key frame
|
//Write the packet to our video store
|
||||||
if ( ret < 0 ) {//Less than zero and we skipped a frame
|
//FIXME no relevance of last key frame
|
||||||
av_free_packet( &packet );
|
int ret = videoStore->writeAudioFramePacket( &packet );
|
||||||
return 0;
|
if ( ret < 0 ) {//Less than zero and we skipped a frame
|
||||||
}
|
Warning("Failure to write audio packet.");
|
||||||
} else {
|
zm_av_packet_unref( &packet );
|
||||||
Debug(4, "Not recording audio packet" );
|
return 0;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Debug(4, "Not recording audio packet" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
av_free_packet( &packet );
|
} else {
|
||||||
} // end while ! frameComplete
|
#if LIBAVUTIL_VERSION_CHECK(56, 23, 0, 23, 0)
|
||||||
return (frameCount);
|
Debug( 3, "Some other stream index %d, %s", packet.stream_index, av_get_media_type_string( mFormatContext->streams[packet.stream_index]->codecpar->codec_type) );
|
||||||
}
|
#else
|
||||||
|
Debug( 3, "Some other stream index %d", packet.stream_index );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
//if ( videoStore ) {
|
||||||
|
|
||||||
|
// the packet contents are ref counted... when queuing, we allocate another packet and reference it with that one, so we should always need to unref here, which should not affect the queued version.
|
||||||
|
zm_av_packet_unref( &packet );
|
||||||
|
//}
|
||||||
|
} // end while ! frameComplete
|
||||||
|
return (frameCount);
|
||||||
|
} // end FfmpegCamera::CaptureAndRecord
|
||||||
|
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
|
|
@ -26,68 +26,81 @@
|
||||||
//#include "zm_utils.h"
|
//#include "zm_utils.h"
|
||||||
#include "zm_ffmpeg.h"
|
#include "zm_ffmpeg.h"
|
||||||
#include "zm_videostore.h"
|
#include "zm_videostore.h"
|
||||||
|
#include "zm_packetqueue.h"
|
||||||
|
|
||||||
//
|
//
|
||||||
// Class representing 'ffmpeg' cameras, i.e. those which are
|
// Class representing 'ffmpeg' cameras, i.e. those which are
|
||||||
// accessed using ffmpeg multimedia framework
|
// accessed using ffmpeg multimedia framework
|
||||||
//
|
//
|
||||||
class FfmpegCamera : public Camera
|
class FfmpegCamera : public Camera {
|
||||||
{
|
protected:
|
||||||
protected:
|
std::string mPath;
|
||||||
std::string mPath;
|
std::string mMethod;
|
||||||
std::string mMethod;
|
std::string mOptions;
|
||||||
std::string mOptions;
|
|
||||||
|
|
||||||
int frameCount;
|
int frameCount;
|
||||||
|
|
||||||
#if HAVE_LIBAVFORMAT
|
#if HAVE_LIBAVFORMAT
|
||||||
AVFormatContext *mFormatContext;
|
AVFormatContext *mFormatContext;
|
||||||
int mVideoStreamId;
|
int mVideoStreamId;
|
||||||
int mAudioStreamId;
|
int mAudioStreamId;
|
||||||
AVCodecContext *mCodecContext;
|
AVCodecContext *mVideoCodecContext;
|
||||||
AVCodec *mCodec;
|
AVCodecContext *mAudioCodecContext;
|
||||||
AVFrame *mRawFrame;
|
AVCodec *mVideoCodec;
|
||||||
AVFrame *mFrame;
|
AVCodec *mAudioCodec;
|
||||||
_AVPIXELFORMAT imagePixFormat;
|
AVFrame *mRawFrame;
|
||||||
|
AVFrame *mFrame;
|
||||||
|
_AVPIXELFORMAT imagePixFormat;
|
||||||
|
|
||||||
int OpenFfmpeg();
|
// Need to keep track of these because apparently the stream can start with values for pts/dts and then subsequent packets start at zero.
|
||||||
int ReopenFfmpeg();
|
int64_t audio_last_pts;
|
||||||
int CloseFfmpeg();
|
int64_t audio_last_dts;
|
||||||
static int FfmpegInterruptCallback(void *ctx);
|
int64_t video_last_pts;
|
||||||
static void* ReopenFfmpegThreadCallback(void *ctx);
|
int64_t video_last_dts;
|
||||||
bool mIsOpening;
|
|
||||||
bool mCanCapture;
|
// Used to store the incoming packet, it will get copied when queued.
|
||||||
int mOpenStart;
|
// We only ever need one at a time, so instead of constantly allocating
|
||||||
pthread_t mReopenThread;
|
// and freeing this structure, we will just make it a member of the object.
|
||||||
|
AVPacket packet;
|
||||||
|
|
||||||
|
int OpenFfmpeg();
|
||||||
|
int ReopenFfmpeg();
|
||||||
|
int CloseFfmpeg();
|
||||||
|
static int FfmpegInterruptCallback(void *ctx);
|
||||||
|
static void* ReopenFfmpegThreadCallback(void *ctx);
|
||||||
|
bool mIsOpening;
|
||||||
|
bool mCanCapture;
|
||||||
|
int mOpenStart;
|
||||||
|
pthread_t mReopenThread;
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
|
||||||
bool wasRecording;
|
VideoStore *videoStore;
|
||||||
VideoStore *videoStore;
|
char oldDirectory[4096];
|
||||||
char oldDirectory[4096];
|
unsigned int old_event_id;
|
||||||
//AVPacket lastKeyframePkt;
|
zm_packetqueue packetqueue;
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
#if HAVE_LIBSWSCALE
|
||||||
struct SwsContext *mConvertContext;
|
struct SwsContext *mConvertContext;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int64_t startTime;
|
int64_t startTime;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
|
FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
|
||||||
~FfmpegCamera();
|
~FfmpegCamera();
|
||||||
|
|
||||||
const std::string &Path() const { return( mPath ); }
|
const std::string &Path() const { return( mPath ); }
|
||||||
const std::string &Options() const { return( mOptions ); }
|
const std::string &Options() const { return( mOptions ); }
|
||||||
const std::string &Method() const { return( mMethod ); }
|
const std::string &Method() const { return( mMethod ); }
|
||||||
|
|
||||||
void Initialise();
|
void Initialise();
|
||||||
void Terminate();
|
void Terminate();
|
||||||
|
|
||||||
int PrimeCapture();
|
int PrimeCapture();
|
||||||
int PreCapture();
|
int PreCapture();
|
||||||
int Capture( Image &image );
|
int Capture( Image &image );
|
||||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
|
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
|
||||||
int PostCapture();
|
int PostCapture();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZM_FFMPEG_CAMERA_H
|
#endif // ZM_FFMPEG_CAMERA_H
|
||||||
|
|
|
@ -87,5 +87,5 @@ int FileCamera::Capture( Image &image )
|
||||||
|
|
||||||
int FileCamera::PostCapture()
|
int FileCamera::PostCapture()
|
||||||
{
|
{
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "zm_camera.h"
|
#include "zm_camera.h"
|
||||||
#include "zm_buffer.h"
|
#include "zm_buffer.h"
|
||||||
#include "zm_regexp.h"
|
#include "zm_regexp.h"
|
||||||
|
#include "zm_packetqueue.h"
|
||||||
|
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ public:
|
||||||
int PreCapture();
|
int PreCapture();
|
||||||
int Capture( Image &image );
|
int Capture( Image &image );
|
||||||
int PostCapture();
|
int PostCapture();
|
||||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
|
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZM_FILE_CAMERA_H
|
#endif // ZM_FILE_CAMERA_H
|
||||||
|
|
2084
src/zm_image.cpp
2084
src/zm_image.cpp
File diff suppressed because it is too large
Load Diff
|
@ -27,11 +27,11 @@ void* LibvlcLockBuffer(void* opaque, void** planes)
|
||||||
{
|
{
|
||||||
LibvlcPrivateData* data = (LibvlcPrivateData*)opaque;
|
LibvlcPrivateData* data = (LibvlcPrivateData*)opaque;
|
||||||
data->mutex.lock();
|
data->mutex.lock();
|
||||||
|
|
||||||
uint8_t* buffer = data->buffer;
|
uint8_t* buffer = data->buffer;
|
||||||
data->buffer = data->prevBuffer;
|
data->buffer = data->prevBuffer;
|
||||||
data->prevBuffer = buffer;
|
data->prevBuffer = buffer;
|
||||||
|
|
||||||
*planes = data->buffer;
|
*planes = data->buffer;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ void* LibvlcLockBuffer(void* opaque, void** planes)
|
||||||
void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes)
|
void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes)
|
||||||
{
|
{
|
||||||
LibvlcPrivateData* data = (LibvlcPrivateData*)opaque;
|
LibvlcPrivateData* data = (LibvlcPrivateData*)opaque;
|
||||||
|
|
||||||
bool newFrame = false;
|
bool newFrame = false;
|
||||||
for(uint32_t i = 0; i < data->bufferSize; i++)
|
for(uint32_t i = 0; i < data->bufferSize; i++)
|
||||||
{
|
{
|
||||||
|
@ -50,7 +50,7 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data->mutex.unlock();
|
data->mutex.unlock();
|
||||||
|
|
||||||
time_t now;
|
time_t now;
|
||||||
time(&now);
|
time(&now);
|
||||||
// Return frames slightly faster than 1fps (if time() supports greater than one second resolution)
|
// Return frames slightly faster than 1fps (if time() supports greater than one second resolution)
|
||||||
|
@ -89,7 +89,7 @@ LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::stri
|
||||||
} else {
|
} else {
|
||||||
Panic("Unexpected colours: %d",colours);
|
Panic("Unexpected colours: %d",colours);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( capture )
|
if ( capture )
|
||||||
{
|
{
|
||||||
Initialise();
|
Initialise();
|
||||||
|
@ -143,9 +143,9 @@ void LibvlcCamera::Terminate()
|
||||||
int LibvlcCamera::PrimeCapture()
|
int LibvlcCamera::PrimeCapture()
|
||||||
{
|
{
|
||||||
Info("Priming capture from %s", mPath.c_str());
|
Info("Priming capture from %s", mPath.c_str());
|
||||||
|
|
||||||
StringVector opVect = split(Options(), ",");
|
StringVector opVect = split(Options(), ",");
|
||||||
|
|
||||||
// Set transport method as specified by method field, rtpUni is default
|
// Set transport method as specified by method field, rtpUni is default
|
||||||
if ( Method() == "rtpMulti" )
|
if ( Method() == "rtpMulti" )
|
||||||
opVect.push_back("--rtsp-mcast");
|
opVect.push_back("--rtsp-mcast");
|
||||||
|
@ -168,11 +168,11 @@ int LibvlcCamera::PrimeCapture()
|
||||||
mLibvlcInstance = libvlc_new (opVect.size(), (const char* const*)mOptArgV);
|
mLibvlcInstance = libvlc_new (opVect.size(), (const char* const*)mOptArgV);
|
||||||
if(mLibvlcInstance == NULL)
|
if(mLibvlcInstance == NULL)
|
||||||
Fatal("Unable to create libvlc instance due to: %s", libvlc_errmsg());
|
Fatal("Unable to create libvlc instance due to: %s", libvlc_errmsg());
|
||||||
|
|
||||||
mLibvlcMedia = libvlc_media_new_location(mLibvlcInstance, mPath.c_str());
|
mLibvlcMedia = libvlc_media_new_location(mLibvlcInstance, mPath.c_str());
|
||||||
if(mLibvlcMedia == NULL)
|
if(mLibvlcMedia == NULL)
|
||||||
Fatal("Unable to open input %s due to: %s", mPath.c_str(), libvlc_errmsg());
|
Fatal("Unable to open input %s due to: %s", mPath.c_str(), libvlc_errmsg());
|
||||||
|
|
||||||
mLibvlcMediaPlayer = libvlc_media_player_new_from_media(mLibvlcMedia);
|
mLibvlcMediaPlayer = libvlc_media_player_new_from_media(mLibvlcMedia);
|
||||||
if(mLibvlcMediaPlayer == NULL)
|
if(mLibvlcMediaPlayer == NULL)
|
||||||
Fatal("Unable to create player for %s due to: %s", mPath.c_str(), libvlc_errmsg());
|
Fatal("Unable to create player for %s due to: %s", mPath.c_str(), libvlc_errmsg());
|
||||||
|
@ -188,12 +188,12 @@ int LibvlcCamera::PrimeCapture()
|
||||||
mLibvlcData.newImage.setValueImmediate(false);
|
mLibvlcData.newImage.setValueImmediate(false);
|
||||||
|
|
||||||
libvlc_media_player_play(mLibvlcMediaPlayer);
|
libvlc_media_player_play(mLibvlcMediaPlayer);
|
||||||
|
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int LibvlcCamera::PreCapture()
|
int LibvlcCamera::PreCapture()
|
||||||
{
|
{
|
||||||
return(0);
|
return(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,12 +207,12 @@ int LibvlcCamera::Capture( Image &image )
|
||||||
image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp);
|
image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp);
|
||||||
mLibvlcData.newImage.setValueImmediate(false);
|
mLibvlcData.newImage.setValueImmediate(false);
|
||||||
mLibvlcData.mutex.unlock();
|
mLibvlcData.mutex.unlock();
|
||||||
|
|
||||||
return (0);
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should not return -1 as cancels capture. Always wait for image if available.
|
// Should not return -1 as cancels capture. Always wait for image if available.
|
||||||
int LibvlcCamera::CaptureAndRecord( Image &image, bool recording, char* event_directory )
|
int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory)
|
||||||
{
|
{
|
||||||
while(!mLibvlcData.newImage.getValueImmediate())
|
while(!mLibvlcData.newImage.getValueImmediate())
|
||||||
mLibvlcData.newImage.getUpdatedValue(1);
|
mLibvlcData.newImage.getUpdatedValue(1);
|
||||||
|
|
|
@ -70,7 +70,7 @@ public:
|
||||||
int PrimeCapture();
|
int PrimeCapture();
|
||||||
int PreCapture();
|
int PreCapture();
|
||||||
int Capture( Image &image );
|
int Capture( Image &image );
|
||||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
|
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
|
||||||
int PostCapture();
|
int PostCapture();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -23,6 +23,7 @@
|
||||||
#include "zm.h"
|
#include "zm.h"
|
||||||
#include "zm_camera.h"
|
#include "zm_camera.h"
|
||||||
#include "zm_image.h"
|
#include "zm_image.h"
|
||||||
|
#include "zm_packetqueue.h"
|
||||||
|
|
||||||
#if ZM_HAS_V4L
|
#if ZM_HAS_V4L
|
||||||
|
|
||||||
|
@ -52,31 +53,31 @@ class LocalCamera : public Camera
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
#if ZM_HAS_V4L2
|
#if ZM_HAS_V4L2
|
||||||
struct V4L2MappedBuffer
|
struct V4L2MappedBuffer
|
||||||
{
|
{
|
||||||
void *start;
|
void *start;
|
||||||
size_t length;
|
size_t length;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct V4L2Data
|
struct V4L2Data
|
||||||
{
|
{
|
||||||
v4l2_cropcap cropcap;
|
v4l2_cropcap cropcap;
|
||||||
v4l2_crop crop;
|
v4l2_crop crop;
|
||||||
v4l2_format fmt;
|
v4l2_format fmt;
|
||||||
v4l2_requestbuffers reqbufs;
|
v4l2_requestbuffers reqbufs;
|
||||||
V4L2MappedBuffer *buffers;
|
V4L2MappedBuffer *buffers;
|
||||||
v4l2_buffer *bufptr;
|
v4l2_buffer *bufptr;
|
||||||
};
|
};
|
||||||
#endif // ZM_HAS_V4L2
|
#endif // ZM_HAS_V4L2
|
||||||
|
|
||||||
#if ZM_HAS_V4L1
|
#if ZM_HAS_V4L1
|
||||||
struct V4L1Data
|
struct V4L1Data
|
||||||
{
|
{
|
||||||
int active_frame;
|
int active_frame;
|
||||||
video_mbuf frames;
|
video_mbuf frames;
|
||||||
video_mmap *buffers;
|
video_mmap *buffers;
|
||||||
unsigned char *bufptr;
|
unsigned char *bufptr;
|
||||||
};
|
};
|
||||||
#endif // ZM_HAS_V4L1
|
#endif // ZM_HAS_V4L1
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -104,21 +105,21 @@ protected:
|
||||||
unsigned int v4l_captures_per_frame;
|
unsigned int v4l_captures_per_frame;
|
||||||
|
|
||||||
#if ZM_HAS_V4L2
|
#if ZM_HAS_V4L2
|
||||||
static V4L2Data v4l2_data;
|
static V4L2Data v4l2_data;
|
||||||
#endif // ZM_HAS_V4L2
|
#endif // ZM_HAS_V4L2
|
||||||
#if ZM_HAS_V4L1
|
#if ZM_HAS_V4L1
|
||||||
static V4L1Data v4l1_data;
|
static V4L1Data v4l1_data;
|
||||||
#endif // ZM_HAS_V4L1
|
#endif // ZM_HAS_V4L1
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
#if HAVE_LIBSWSCALE
|
||||||
static AVFrame **capturePictures;
|
static AVFrame **capturePictures;
|
||||||
_AVPIXELFORMAT imagePixFormat;
|
_AVPIXELFORMAT imagePixFormat;
|
||||||
_AVPIXELFORMAT capturePixFormat;
|
_AVPIXELFORMAT capturePixFormat;
|
||||||
struct SwsContext *imgConversionContext;
|
struct SwsContext *imgConversionContext;
|
||||||
AVFrame *tmpPicture;
|
AVFrame *tmpPicture;
|
||||||
#endif // HAVE_LIBSWSCALE
|
#endif // HAVE_LIBSWSCALE
|
||||||
|
|
||||||
static LocalCamera *last_camera;
|
static LocalCamera *last_camera;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LocalCamera(
|
LocalCamera(
|
||||||
|
@ -161,7 +162,7 @@ public:
|
||||||
int PreCapture();
|
int PreCapture();
|
||||||
int Capture( Image &image );
|
int Capture( Image &image );
|
||||||
int PostCapture();
|
int PostCapture();
|
||||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
|
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);};
|
||||||
|
|
||||||
static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose );
|
static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose );
|
||||||
};
|
};
|
||||||
|
|
1561
src/zm_monitor.cpp
1561
src/zm_monitor.cpp
File diff suppressed because it is too large
Load Diff
125
src/zm_monitor.h
125
src/zm_monitor.h
|
@ -46,20 +46,17 @@ class Monitor;
|
||||||
// This is the main class for monitors. Each monitor is associated
|
// This is the main class for monitors. Each monitor is associated
|
||||||
// with a camera and is effectively a collector for events.
|
// with a camera and is effectively a collector for events.
|
||||||
//
|
//
|
||||||
class Monitor
|
class Monitor {
|
||||||
{
|
friend class MonitorStream;
|
||||||
friend class MonitorStream;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef enum
|
typedef enum {
|
||||||
{
|
|
||||||
QUERY=0,
|
QUERY=0,
|
||||||
CAPTURE,
|
CAPTURE,
|
||||||
ANALYSIS
|
ANALYSIS
|
||||||
} Purpose;
|
} Purpose;
|
||||||
|
|
||||||
typedef enum
|
typedef enum {
|
||||||
{
|
|
||||||
NONE=1,
|
NONE=1,
|
||||||
MONITOR,
|
MONITOR,
|
||||||
MODECT,
|
MODECT,
|
||||||
|
@ -68,8 +65,7 @@ public:
|
||||||
NODECT
|
NODECT
|
||||||
} Function;
|
} Function;
|
||||||
|
|
||||||
typedef enum
|
typedef enum {
|
||||||
{
|
|
||||||
ROTATE_0=1,
|
ROTATE_0=1,
|
||||||
ROTATE_90,
|
ROTATE_90,
|
||||||
ROTATE_180,
|
ROTATE_180,
|
||||||
|
@ -78,8 +74,7 @@ public:
|
||||||
FLIP_VERT
|
FLIP_VERT
|
||||||
} Orientation;
|
} Orientation;
|
||||||
|
|
||||||
typedef enum
|
typedef enum {
|
||||||
{
|
|
||||||
IDLE,
|
IDLE,
|
||||||
PREALARM,
|
PREALARM,
|
||||||
ALARM,
|
ALARM,
|
||||||
|
@ -87,6 +82,12 @@ public:
|
||||||
TAPE
|
TAPE
|
||||||
} State;
|
} State;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DISABLED,
|
||||||
|
X264ENCODE,
|
||||||
|
H264PASSTHROUGH,
|
||||||
|
} VideoWriter;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef std::set<Zone *> ZoneSet;
|
typedef std::set<Zone *> ZoneSet;
|
||||||
|
|
||||||
|
@ -95,8 +96,7 @@ protected:
|
||||||
typedef enum { CLOSE_TIME, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode;
|
typedef enum { CLOSE_TIME, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode;
|
||||||
|
|
||||||
/* sizeof(SharedData) expected to be 336 bytes on 32bit and 64bit */
|
/* sizeof(SharedData) expected to be 336 bytes on 32bit and 64bit */
|
||||||
typedef struct
|
typedef struct {
|
||||||
{
|
|
||||||
uint32_t size; /* +0 */
|
uint32_t size; /* +0 */
|
||||||
uint32_t last_write_index; /* +4 */
|
uint32_t last_write_index; /* +4 */
|
||||||
uint32_t last_read_index; /* +8 */
|
uint32_t last_read_index; /* +8 */
|
||||||
|
@ -121,12 +121,12 @@ protected:
|
||||||
** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16.
|
** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16.
|
||||||
*/
|
*/
|
||||||
union { /* +64 */
|
union { /* +64 */
|
||||||
time_t last_write_time;
|
time_t last_write_time;
|
||||||
uint64_t extrapad1;
|
uint64_t extrapad1;
|
||||||
};
|
};
|
||||||
union { /* +72 */
|
union { /* +72 */
|
||||||
time_t last_read_time;
|
time_t last_read_time;
|
||||||
uint64_t extrapad2;
|
uint64_t extrapad2;
|
||||||
};
|
};
|
||||||
uint8_t control_state[256]; /* +80 */
|
uint8_t control_state[256]; /* +80 */
|
||||||
|
|
||||||
|
@ -135,8 +135,7 @@ protected:
|
||||||
typedef enum { TRIGGER_CANCEL, TRIGGER_ON, TRIGGER_OFF } TriggerState;
|
typedef enum { TRIGGER_CANCEL, TRIGGER_ON, TRIGGER_OFF } TriggerState;
|
||||||
|
|
||||||
/* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */
|
/* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */
|
||||||
typedef struct
|
typedef struct {
|
||||||
{
|
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint32_t trigger_state;
|
uint32_t trigger_state;
|
||||||
uint32_t trigger_score;
|
uint32_t trigger_score;
|
||||||
|
@ -147,28 +146,25 @@ protected:
|
||||||
} TriggerData;
|
} TriggerData;
|
||||||
|
|
||||||
/* sizeof(Snapshot) expected to be 16 bytes on 32bit and 32 bytes on 64bit */
|
/* sizeof(Snapshot) expected to be 16 bytes on 32bit and 32 bytes on 64bit */
|
||||||
struct Snapshot
|
struct Snapshot {
|
||||||
{
|
|
||||||
struct timeval *timestamp;
|
struct timeval *timestamp;
|
||||||
Image *image;
|
Image *image;
|
||||||
void* padding;
|
void* padding;
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat
|
//TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat
|
||||||
#if 1
|
#if 1
|
||||||
//sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit
|
//sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit
|
||||||
typedef struct
|
typedef struct {
|
||||||
{
|
uint32_t size;
|
||||||
uint32_t size;
|
char event_file[4096];
|
||||||
char event_file[4096];
|
timeval recording; //bool arch dependent so use uint32 instead
|
||||||
uint32_t recording; //bool arch dependent so use uint32 instead
|
//uint32_t frameNumber;
|
||||||
//uint32_t frameNumber;
|
} VideoStoreData;
|
||||||
} VideoStoreData;
|
|
||||||
|
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
|
||||||
class MonitorLink
|
class MonitorLink {
|
||||||
{
|
|
||||||
protected:
|
protected:
|
||||||
unsigned int id;
|
unsigned int id;
|
||||||
char name[64];
|
char name[64];
|
||||||
|
@ -196,21 +192,17 @@ protected:
|
||||||
MonitorLink( int p_id, const char *p_name );
|
MonitorLink( int p_id, const char *p_name );
|
||||||
~MonitorLink();
|
~MonitorLink();
|
||||||
|
|
||||||
inline int Id() const
|
inline int Id() const {
|
||||||
{
|
|
||||||
return( id );
|
return( id );
|
||||||
}
|
}
|
||||||
inline const char *Name() const
|
inline const char *Name() const {
|
||||||
{
|
|
||||||
return( name );
|
return( name );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool isConnected() const
|
inline bool isConnected() const {
|
||||||
{
|
|
||||||
return( connected );
|
return( connected );
|
||||||
}
|
}
|
||||||
inline time_t getLastConnectTime() const
|
inline time_t getLastConnectTime() const {
|
||||||
{
|
|
||||||
return( last_connect_time );
|
return( last_connect_time );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +229,7 @@ protected:
|
||||||
unsigned int deinterlacing;
|
unsigned int deinterlacing;
|
||||||
|
|
||||||
int savejpegspref;
|
int savejpegspref;
|
||||||
int videowriterpref;
|
VideoWriter videowriter;
|
||||||
std::string encoderparams;
|
std::string encoderparams;
|
||||||
std::vector<EncoderParameter_t> encoderparamsvec;
|
std::vector<EncoderParameter_t> encoderparamsvec;
|
||||||
bool record_audio; // Whether to store the audio that we receive
|
bool record_audio; // Whether to store the audio that we receive
|
||||||
|
@ -271,7 +263,7 @@ protected:
|
||||||
int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm.
|
int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm.
|
||||||
bool track_motion; // Whether this monitor tries to track detected motion
|
bool track_motion; // Whether this monitor tries to track detected motion
|
||||||
Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected
|
Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected
|
||||||
bool embed_exif; // Whether to embed Exif data into each image frame or not
|
bool embed_exif; // Whether to embed Exif data into each image frame or not
|
||||||
|
|
||||||
double fps;
|
double fps;
|
||||||
Image delta_image;
|
Image delta_image;
|
||||||
|
@ -291,7 +283,7 @@ protected:
|
||||||
time_t start_time;
|
time_t start_time;
|
||||||
time_t last_fps_time;
|
time_t last_fps_time;
|
||||||
time_t auto_resume_time;
|
time_t auto_resume_time;
|
||||||
unsigned int last_motion_score;
|
unsigned int last_motion_score;
|
||||||
|
|
||||||
EventCloseMode event_close_mode;
|
EventCloseMode event_close_mode;
|
||||||
|
|
||||||
|
@ -342,7 +334,7 @@ public:
|
||||||
int p_orientation,
|
int p_orientation,
|
||||||
unsigned int p_deinterlacing,
|
unsigned int p_deinterlacing,
|
||||||
int p_savejpegs,
|
int p_savejpegs,
|
||||||
int p_videowriter,
|
VideoWriter p_videowriter,
|
||||||
std::string p_encoderparams,
|
std::string p_encoderparams,
|
||||||
bool p_record_audio,
|
bool p_record_audio,
|
||||||
const char *p_event_prefix,
|
const char *p_event_prefix,
|
||||||
|
@ -378,47 +370,38 @@ public:
|
||||||
void AddPrivacyBitmask( Zone *p_zones[] );
|
void AddPrivacyBitmask( Zone *p_zones[] );
|
||||||
|
|
||||||
bool connect();
|
bool connect();
|
||||||
inline int ShmValid() const
|
inline int ShmValid() const {
|
||||||
{
|
|
||||||
return( shared_data->valid );
|
return( shared_data->valid );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int Id() const
|
inline int Id() const {
|
||||||
{
|
|
||||||
return( id );
|
return( id );
|
||||||
}
|
}
|
||||||
inline const char *Name() const
|
inline const char *Name() const {
|
||||||
{
|
|
||||||
return( name );
|
return( name );
|
||||||
}
|
}
|
||||||
inline Function GetFunction() const
|
inline Function GetFunction() const {
|
||||||
{
|
|
||||||
return( function );
|
return( function );
|
||||||
}
|
}
|
||||||
inline bool Enabled()
|
inline bool Enabled() {
|
||||||
{
|
|
||||||
if ( function <= MONITOR )
|
if ( function <= MONITOR )
|
||||||
return( false );
|
return( false );
|
||||||
return( enabled );
|
return( enabled );
|
||||||
}
|
}
|
||||||
inline const char *EventPrefix() const
|
inline const char *EventPrefix() const {
|
||||||
{
|
|
||||||
return( event_prefix );
|
return( event_prefix );
|
||||||
}
|
}
|
||||||
inline bool Ready()
|
inline bool Ready() {
|
||||||
{
|
|
||||||
if ( function <= MONITOR )
|
if ( function <= MONITOR )
|
||||||
return( false );
|
return( false );
|
||||||
return( image_count > ready_count );
|
return( image_count > ready_count );
|
||||||
}
|
}
|
||||||
inline bool Active()
|
inline bool Active() {
|
||||||
{
|
|
||||||
if ( function <= MONITOR )
|
if ( function <= MONITOR )
|
||||||
return( false );
|
return( false );
|
||||||
return( enabled && shared_data->active );
|
return( enabled && shared_data->active );
|
||||||
}
|
}
|
||||||
inline bool Exif()
|
inline bool Exif() {
|
||||||
{
|
|
||||||
return( embed_exif );
|
return( embed_exif );
|
||||||
}
|
}
|
||||||
Orientation getOrientation() const;
|
Orientation getOrientation() const;
|
||||||
|
@ -429,9 +412,10 @@ public:
|
||||||
unsigned int SubpixelOrder() const;
|
unsigned int SubpixelOrder() const;
|
||||||
|
|
||||||
int GetOptSaveJPEGs() const { return( savejpegspref ); }
|
int GetOptSaveJPEGs() const { return( savejpegspref ); }
|
||||||
int GetOptVideoWriter() const { return( videowriterpref ); }
|
VideoWriter GetOptVideoWriter() const { return( videowriter ); }
|
||||||
const std::vector<EncoderParameter_t>* GetOptEncoderParams() const { return( &encoderparamsvec ); }
|
const std::vector<EncoderParameter_t>* GetOptEncoderParams() const { return( &encoderparamsvec ); }
|
||||||
|
|
||||||
|
unsigned int GetPreEventCount() const { return pre_event_count; };
|
||||||
State GetState() const;
|
State GetState() const;
|
||||||
int GetImage( int index=-1, int scale=100 );
|
int GetImage( int index=-1, int scale=100 );
|
||||||
struct timeval GetTimestamp( int index=-1 ) const;
|
struct timeval GetTimestamp( int index=-1 ) const;
|
||||||
|
@ -504,8 +488,7 @@ public:
|
||||||
|
|
||||||
#define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit))
|
#define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit))
|
||||||
|
|
||||||
class MonitorStream : public StreamBase
|
class MonitorStream : public StreamBase {
|
||||||
{
|
|
||||||
protected:
|
protected:
|
||||||
typedef struct SwapImage {
|
typedef struct SwapImage {
|
||||||
bool valid;
|
bool valid;
|
||||||
|
@ -536,19 +519,15 @@ protected:
|
||||||
void processCommand( const CmdMsg *msg );
|
void processCommand( const CmdMsg *msg );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 )
|
MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
void setStreamBuffer( int p_playback_buffer )
|
void setStreamBuffer( int p_playback_buffer ) {
|
||||||
{
|
|
||||||
playback_buffer = p_playback_buffer;
|
playback_buffer = p_playback_buffer;
|
||||||
}
|
}
|
||||||
void setStreamTTL( time_t p_ttl )
|
void setStreamTTL( time_t p_ttl ) {
|
||||||
{
|
|
||||||
ttl = p_ttl;
|
ttl = p_ttl;
|
||||||
}
|
}
|
||||||
bool setStreamStart( int monitor_id )
|
bool setStreamStart( int monitor_id ) {
|
||||||
{
|
|
||||||
return loadMonitor( monitor_id );
|
return loadMonitor( monitor_id );
|
||||||
}
|
}
|
||||||
void runStream();
|
void runStream();
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
//ZoneMinder Packet Implementation Class
|
||||||
|
//Copyright 2017 ZoneMinder LLC
|
||||||
|
//
|
||||||
|
//This file is part of ZoneMinder.
|
||||||
|
//
|
||||||
|
//ZoneMinder is free software: you can redistribute it and/or modify
|
||||||
|
//it under the terms of the GNU General Public License as published by
|
||||||
|
//the Free Software Foundation, either version 3 of the License, or
|
||||||
|
//(at your option) any later version.
|
||||||
|
//
|
||||||
|
//ZoneMinder is distributed in the hope that it will be useful,
|
||||||
|
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
//GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
//You should have received a copy of the GNU General Public License
|
||||||
|
//along with ZoneMinder. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "zm_packet.h"
|
||||||
|
#include "zm_ffmpeg.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
ZMPacket::ZMPacket( AVPacket *p ) {
|
||||||
|
av_init_packet( &packet );
|
||||||
|
if ( zm_av_packet_ref( &packet, p ) < 0 ) {
|
||||||
|
Error("error refing packet");
|
||||||
|
}
|
||||||
|
gettimeofday( ×tamp, NULL );
|
||||||
|
}
|
||||||
|
|
||||||
|
ZMPacket::ZMPacket( AVPacket *p, struct timeval *t ) {
|
||||||
|
av_init_packet( &packet );
|
||||||
|
if ( zm_av_packet_ref( &packet, p ) < 0 ) {
|
||||||
|
Error("error refing packet");
|
||||||
|
}
|
||||||
|
timestamp = *t;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZMPacket::~ZMPacket() {
|
||||||
|
zm_av_packet_unref( &packet );
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
//ZoneMinder Packet Wrapper Class
|
||||||
|
//Copyright 2017 ZoneMinder LLC
|
||||||
|
//
|
||||||
|
//This file is part of ZoneMinder.
|
||||||
|
//
|
||||||
|
//ZoneMinder is free software: you can redistribute it and/or modify
|
||||||
|
//it under the terms of the GNU General Public License as published by
|
||||||
|
//the Free Software Foundation, either version 3 of the License, or
|
||||||
|
//(at your option) any later version.
|
||||||
|
//
|
||||||
|
//ZoneMinder is distributed in the hope that it will be useful,
|
||||||
|
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
//GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
//You should have received a copy of the GNU General Public License
|
||||||
|
//along with ZoneMinder. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef ZM_PACKET_H
|
||||||
|
#define ZM_PACKET_H
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZMPacket {
|
||||||
|
public:
|
||||||
|
|
||||||
|
AVPacket packet;
|
||||||
|
struct timeval timestamp;
|
||||||
|
public:
|
||||||
|
AVPacket *av_packet() { return &packet; }
|
||||||
|
ZMPacket( AVPacket *packet, struct timeval *timestamp );
|
||||||
|
ZMPacket( AVPacket *packet );
|
||||||
|
~ZMPacket();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* ZM_PACKET_H */
|
|
@ -0,0 +1,152 @@
|
||||||
|
//ZoneMinder Packet Queue Implementation Class
|
||||||
|
//Copyright 2016 Steve Gilvarry
|
||||||
|
//
|
||||||
|
//This file is part of ZoneMinder.
|
||||||
|
//
|
||||||
|
//ZoneMinder is free software: you can redistribute it and/or modify
|
||||||
|
//it under the terms of the GNU General Public License as published by
|
||||||
|
//the Free Software Foundation, either version 3 of the License, or
|
||||||
|
//(at your option) any later version.
|
||||||
|
//
|
||||||
|
//ZoneMinder is distributed in the hope that it will be useful,
|
||||||
|
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
//GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
//You should have received a copy of the GNU General Public License
|
||||||
|
//along with ZoneMinder. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "zm_packetqueue.h"
|
||||||
|
#include "zm_ffmpeg.h"
|
||||||
|
|
||||||
|
#define VIDEO_QUEUESIZE 200
|
||||||
|
#define AUDIO_QUEUESIZE 50
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
zm_packetqueue::zm_packetqueue(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
zm_packetqueue::~zm_packetqueue() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool zm_packetqueue::queuePacket( ZMPacket* zm_packet ) {
|
||||||
|
pktQueue.push_back( zm_packet );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool zm_packetqueue::queuePacket( AVPacket* av_packet ) {
|
||||||
|
|
||||||
|
ZMPacket *zm_packet = new ZMPacket( av_packet );
|
||||||
|
|
||||||
|
pktQueue.push_back( zm_packet );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZMPacket* zm_packetqueue::popPacket( ) {
|
||||||
|
if ( pktQueue.empty() ) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZMPacket *packet = pktQueue.front();
|
||||||
|
pktQueue.pop_front();
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) {
|
||||||
|
|
||||||
|
Debug(3, "Clearing all but %d frames", frames_to_keep );
|
||||||
|
frames_to_keep += 1;
|
||||||
|
|
||||||
|
if ( pktQueue.empty() ) {
|
||||||
|
Debug(3, "Queue is empty");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
Debug(3, "Queue has (%d)", pktQueue.size() );
|
||||||
|
}
|
||||||
|
|
||||||
|
list<ZMPacket *>::reverse_iterator it;
|
||||||
|
ZMPacket *packet = NULL;
|
||||||
|
|
||||||
|
for ( it = pktQueue.rbegin(); it != pktQueue.rend() && frames_to_keep; ++it ) {
|
||||||
|
ZMPacket *zm_packet = *it;
|
||||||
|
AVPacket *av_packet = &(zm_packet->packet);
|
||||||
|
|
||||||
|
Debug(3, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
|
||||||
|
|
||||||
|
// Want frames_to_keep video keyframes. Otherwise, we may not have enough
|
||||||
|
if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) {
|
||||||
|
if (!frames_to_keep)
|
||||||
|
break;
|
||||||
|
frames_to_keep --;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsigned int delete_count = 0;
|
||||||
|
while ( it != pktQueue.rend() ) {
|
||||||
|
Debug(3, "Deleting a packet from the front, count is (%d)", delete_count );
|
||||||
|
|
||||||
|
packet = pktQueue.front();
|
||||||
|
pktQueue.pop_front();
|
||||||
|
delete packet;
|
||||||
|
|
||||||
|
delete_count += 1;
|
||||||
|
}
|
||||||
|
return delete_count;
|
||||||
|
} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id )
|
||||||
|
|
||||||
|
void zm_packetqueue::clearQueue() {
|
||||||
|
ZMPacket *packet = NULL;
|
||||||
|
while(!pktQueue.empty()) {
|
||||||
|
packet = pktQueue.front();
|
||||||
|
pktQueue.pop_front();
|
||||||
|
delete packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int zm_packetqueue::size() {
|
||||||
|
return pktQueue.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) {
|
||||||
|
// Need to find the keyframe <= recording_started. Can get rid of audio packets.
|
||||||
|
if ( pktQueue.empty() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1 - find keyframe < recording_started.
|
||||||
|
// Step 2 - pop packets until we get to the packet in step 2
|
||||||
|
list<ZMPacket *>::reverse_iterator it;
|
||||||
|
|
||||||
|
for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) {
|
||||||
|
ZMPacket *zm_packet = *it;
|
||||||
|
AVPacket *av_packet = &(zm_packet->packet);
|
||||||
|
Debug(1, "Looking for keyframe after start" );
|
||||||
|
if (
|
||||||
|
( av_packet->flags & AV_PKT_FLAG_KEY )
|
||||||
|
&&
|
||||||
|
( av_packet->stream_index == mVideoStreamId )
|
||||||
|
&&
|
||||||
|
timercmp( &(zm_packet->timestamp), recording_started, < )
|
||||||
|
) {
|
||||||
|
Debug(1, "Found keyframe before start" );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( it == pktQueue.rend() ) {
|
||||||
|
Debug(1, "Didn't find a keyframe packet keeping all" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZMPacket *packet = NULL;
|
||||||
|
while ( pktQueue.rend() != it ) {
|
||||||
|
packet = pktQueue.front();
|
||||||
|
pktQueue.pop_front();
|
||||||
|
delete packet;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
//ZoneMinder Packet Queue Interface Class
|
||||||
|
//Copyright 2016 Steve Gilvarry
|
||||||
|
//
|
||||||
|
//This file is part of ZoneMinder.
|
||||||
|
//
|
||||||
|
//ZoneMinder is free software: you can redistribute it and/or modify
|
||||||
|
//it under the terms of the GNU General Public License as published by
|
||||||
|
//the Free Software Foundation, either version 3 of the License, or
|
||||||
|
//(at your option) any later version.
|
||||||
|
//
|
||||||
|
//ZoneMinder is distributed in the hope that it will be useful,
|
||||||
|
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
//GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
//You should have received a copy of the GNU General Public License
|
||||||
|
//along with ZoneMinder. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef ZM_PACKETQUEUE_H
|
||||||
|
#define ZM_PACKETQUEUE_H
|
||||||
|
|
||||||
|
//#include <boost/interprocess/managed_shared_memory.hpp>
|
||||||
|
//#include <boost/interprocess/containers/map.hpp>
|
||||||
|
//#include <boost/interprocess/allocators/allocator.hpp>
|
||||||
|
#include <list>
|
||||||
|
#include "zm_packet.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
class zm_packetqueue {
|
||||||
|
public:
|
||||||
|
zm_packetqueue();
|
||||||
|
virtual ~zm_packetqueue();
|
||||||
|
bool queuePacket( AVPacket* packet, struct timeval *timestamp );
|
||||||
|
bool queuePacket( ZMPacket* packet );
|
||||||
|
bool queuePacket( AVPacket* packet );
|
||||||
|
ZMPacket * popPacket( );
|
||||||
|
bool popVideoPacket(ZMPacket* packet);
|
||||||
|
bool popAudioPacket(ZMPacket* packet);
|
||||||
|
unsigned int clearQueue( unsigned int video_frames_to_keep, int stream_id );
|
||||||
|
void clearQueue( );
|
||||||
|
unsigned int size();
|
||||||
|
void clear_unwanted_packets( timeval *recording, int mVideoStreamId );
|
||||||
|
private:
|
||||||
|
std::list<ZMPacket *> pktQueue;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* ZM_PACKETQUEUE_H */
|
|
@ -89,7 +89,7 @@ public:
|
||||||
virtual int PreCapture() = 0;
|
virtual int PreCapture() = 0;
|
||||||
virtual int Capture( Image &image ) = 0;
|
virtual int Capture( Image &image ) = 0;
|
||||||
virtual int PostCapture() = 0;
|
virtual int PostCapture() = 0;
|
||||||
virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory )=0;
|
virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory )=0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZM_REMOTE_CAMERA_H
|
#endif // ZM_REMOTE_CAMERA_H
|
||||||
|
|
|
@ -189,7 +189,7 @@ int RemoteCameraHttp::SendRequest()
|
||||||
* > 0 is the # of bytes read.
|
* > 0 is the # of bytes read.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected )
|
int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected )
|
||||||
{
|
{
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
FD_ZERO(&rfds);
|
FD_ZERO(&rfds);
|
||||||
|
|
|
@ -53,12 +53,12 @@ public:
|
||||||
int Connect();
|
int Connect();
|
||||||
int Disconnect();
|
int Disconnect();
|
||||||
int SendRequest();
|
int SendRequest();
|
||||||
int ReadData( Buffer &buffer, int bytes_expected=0 );
|
int ReadData( Buffer &buffer, unsigned int bytes_expected=0 );
|
||||||
int GetResponse();
|
int GetResponse();
|
||||||
int PreCapture();
|
int PreCapture();
|
||||||
int Capture( Image &image );
|
int Capture( Image &image );
|
||||||
int PostCapture();
|
int PostCapture();
|
||||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);};
|
int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZM_REMOTE_CAMERA_HTTP_H
|
#endif // ZM_REMOTE_CAMERA_HTTP_H
|
||||||
|
|
|
@ -58,10 +58,9 @@ RemoteCameraRtsp::RemoteCameraRtsp( unsigned int p_monitor_id, const std::string
|
||||||
mRawFrame = NULL;
|
mRawFrame = NULL;
|
||||||
mFrame = NULL;
|
mFrame = NULL;
|
||||||
frameCount = 0;
|
frameCount = 0;
|
||||||
wasRecording = false;
|
|
||||||
startTime=0;
|
startTime=0;
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
#if HAVE_LIBSWSCALE
|
||||||
mConvertContext = NULL;
|
mConvertContext = NULL;
|
||||||
#endif
|
#endif
|
||||||
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
|
||||||
|
@ -82,13 +81,8 @@ RemoteCameraRtsp::RemoteCameraRtsp( unsigned int p_monitor_id, const std::string
|
||||||
|
|
||||||
RemoteCameraRtsp::~RemoteCameraRtsp()
|
RemoteCameraRtsp::~RemoteCameraRtsp()
|
||||||
{
|
{
|
||||||
#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101)
|
|
||||||
av_frame_free( &mFrame );
|
av_frame_free( &mFrame );
|
||||||
av_frame_free( &mRawFrame );
|
av_frame_free( &mRawFrame );
|
||||||
#else
|
|
||||||
av_freep( &mFrame );
|
|
||||||
av_freep( &mRawFrame );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
#if HAVE_LIBSWSCALE
|
||||||
if ( mConvertContext )
|
if ( mConvertContext )
|
||||||
|
@ -117,7 +111,7 @@ void RemoteCameraRtsp::Initialise()
|
||||||
int max_size = width*height*colours;
|
int max_size = width*height*colours;
|
||||||
|
|
||||||
// This allocates a buffer able to hold a raw fframe, which is a little artbitrary. Might be nice to get some
|
// This allocates a buffer able to hold a raw fframe, which is a little artbitrary. Might be nice to get some
|
||||||
// decent data on how large a buffer is really needed.
|
// decent data on how large a buffer is really needed. I think in ffmpeg there are now some functions to do that.
|
||||||
buffer.size( max_size );
|
buffer.size( max_size );
|
||||||
|
|
||||||
if ( logDebugging() )
|
if ( logDebugging() )
|
||||||
|
@ -172,6 +166,7 @@ int RemoteCameraRtsp::PrimeCapture()
|
||||||
|
|
||||||
// Find first video stream present
|
// Find first video stream present
|
||||||
mVideoStreamId = -1;
|
mVideoStreamId = -1;
|
||||||
|
mAudioStreamId = -1;
|
||||||
|
|
||||||
// Find the first video stream.
|
// Find the first video stream.
|
||||||
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) {
|
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) {
|
||||||
|
@ -181,12 +176,31 @@ int RemoteCameraRtsp::PrimeCapture()
|
||||||
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
|
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
mVideoStreamId = i;
|
if ( mVideoStreamId == -1 ) {
|
||||||
break;
|
mVideoStreamId = i;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
Debug(2, "Have another video stream." );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0))
|
||||||
|
if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO )
|
||||||
|
#else
|
||||||
|
if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO )
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if ( mAudioStreamId == -1 ) {
|
||||||
|
mAudioStreamId = i;
|
||||||
|
} else {
|
||||||
|
Debug(2, "Have another audio stream." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end foreach stream
|
||||||
|
|
||||||
if ( mVideoStreamId == -1 )
|
if ( mVideoStreamId == -1 )
|
||||||
Fatal( "Unable to locate video stream" );
|
Fatal( "Unable to locate video stream" );
|
||||||
|
if ( mAudioStreamId == -1 )
|
||||||
|
Debug( 3, "Unable to locate audio stream" );
|
||||||
|
|
||||||
// Get a pointer to the codec context for the video stream
|
// Get a pointer to the codec context for the video stream
|
||||||
mCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
mCodecContext = mFormatContext->streams[mVideoStreamId]->codec;
|
||||||
|
@ -248,8 +262,7 @@ int RemoteCameraRtsp::PrimeCapture()
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
int RemoteCameraRtsp::PreCapture()
|
int RemoteCameraRtsp::PreCapture() {
|
||||||
{
|
|
||||||
if ( !rtspThread->isRunning() )
|
if ( !rtspThread->isRunning() )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
if ( !rtspThread->hasSources() )
|
if ( !rtspThread->hasSources() )
|
||||||
|
@ -260,8 +273,7 @@ int RemoteCameraRtsp::PreCapture()
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
int RemoteCameraRtsp::Capture( Image &image )
|
int RemoteCameraRtsp::Capture( Image &image ) {
|
||||||
{
|
|
||||||
AVPacket packet;
|
AVPacket packet;
|
||||||
uint8_t* directbuffer;
|
uint8_t* directbuffer;
|
||||||
int frameComplete = false;
|
int frameComplete = false;
|
||||||
|
@ -272,15 +284,13 @@ int RemoteCameraRtsp::Capture( Image &image )
|
||||||
Error("Failed requesting writeable buffer for the captured image.");
|
Error("Failed requesting writeable buffer for the captured image.");
|
||||||
return (-1);
|
return (-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
while ( true )
|
while ( true ) {
|
||||||
{
|
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
if ( !rtspThread->isRunning() )
|
if ( !rtspThread->isRunning() )
|
||||||
return (-1);
|
return (-1);
|
||||||
|
|
||||||
if ( rtspThread->getFrame( buffer ) )
|
if ( rtspThread->getFrame( buffer ) ) {
|
||||||
{
|
|
||||||
Debug( 3, "Read frame %d bytes", buffer.size() );
|
Debug( 3, "Read frame %d bytes", buffer.size() );
|
||||||
Debug( 4, "Address %p", buffer.head() );
|
Debug( 4, "Address %p", buffer.head() );
|
||||||
Hexdump( 4, buffer.head(), 16 );
|
Hexdump( 4, buffer.head(), 16 );
|
||||||
|
@ -288,18 +298,17 @@ int RemoteCameraRtsp::Capture( Image &image )
|
||||||
if ( !buffer.size() )
|
if ( !buffer.size() )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
if(mCodecContext->codec_id == AV_CODEC_ID_H264)
|
if(mCodecContext->codec_id == AV_CODEC_ID_H264) {
|
||||||
{
|
|
||||||
// SPS and PPS frames should be saved and appended to IDR frames
|
// SPS and PPS frames should be saved and appended to IDR frames
|
||||||
int nalType = (buffer.head()[3] & 0x1f);
|
int nalType = (buffer.head()[3] & 0x1f);
|
||||||
|
|
||||||
// SPS
|
// SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures
|
||||||
if(nalType == 7)
|
if(nalType == 7)
|
||||||
{
|
{
|
||||||
lastSps = buffer;
|
lastSps = buffer;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// PPS
|
// PPS The PPS NAL unit contains parameters that apply to the decoding of one or more individual pictures inside a coded video sequence
|
||||||
else if(nalType == 8)
|
else if(nalType == 8)
|
||||||
{
|
{
|
||||||
lastPps = buffer;
|
lastPps = buffer;
|
||||||
|
@ -311,6 +320,8 @@ int RemoteCameraRtsp::Capture( Image &image )
|
||||||
buffer += lastSps;
|
buffer += lastSps;
|
||||||
buffer += lastPps;
|
buffer += lastPps;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Debug(3, "Not an h264 packet");
|
||||||
}
|
}
|
||||||
|
|
||||||
av_init_packet( &packet );
|
av_init_packet( &packet );
|
||||||
|
@ -339,37 +350,35 @@ int RemoteCameraRtsp::Capture( Image &image )
|
||||||
}
|
}
|
||||||
// At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer?
|
// At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer?
|
||||||
if ( frameComplete ) {
|
if ( frameComplete ) {
|
||||||
|
|
||||||
Debug( 3, "Got frame %d", frameCount );
|
Debug( 3, "Got frame %d", frameCount );
|
||||||
|
|
||||||
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height );
|
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height );
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
#if HAVE_LIBSWSCALE
|
||||||
if(mConvertContext == NULL) {
|
if(mConvertContext == NULL) {
|
||||||
mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
|
mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
|
||||||
|
|
||||||
if(mConvertContext == NULL)
|
if(mConvertContext == NULL)
|
||||||
Fatal( "Unable to create conversion context");
|
Fatal( "Unable to create conversion context");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 )
|
if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 )
|
||||||
Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount );
|
Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount );
|
||||||
#else // HAVE_LIBSWSCALE
|
#else // HAVE_LIBSWSCALE
|
||||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" );
|
Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" );
|
||||||
#endif // HAVE_LIBSWSCALE
|
#endif // HAVE_LIBSWSCALE
|
||||||
frameCount++;
|
|
||||||
|
frameCount++;
|
||||||
|
|
||||||
} /* frame complete */
|
} /* frame complete */
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
|
zm_av_packet_unref( &packet );
|
||||||
av_packet_unref( &packet );
|
|
||||||
#else
|
|
||||||
av_free_packet( &packet );
|
|
||||||
#endif
|
|
||||||
} /* getFrame() */
|
} /* getFrame() */
|
||||||
|
|
||||||
if(frameComplete)
|
if(frameComplete)
|
||||||
return (0);
|
return (0);
|
||||||
|
|
||||||
} // end while true
|
} // end while true
|
||||||
|
|
||||||
// can never get here.
|
// can never get here.
|
||||||
|
@ -377,32 +386,63 @@ int RemoteCameraRtsp::Capture( Image &image )
|
||||||
}
|
}
|
||||||
|
|
||||||
//Function to handle capture and store
|
//Function to handle capture and store
|
||||||
int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file ) {
|
|
||||||
|
int RemoteCameraRtsp::CaptureAndRecord(Image &image, timeval recording, char* event_file ) {
|
||||||
AVPacket packet;
|
AVPacket packet;
|
||||||
uint8_t* directbuffer;
|
uint8_t* directbuffer;
|
||||||
int frameComplete = false;
|
int frameComplete = false;
|
||||||
|
|
||||||
/* Request a writeable buffer of the target image */
|
|
||||||
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
|
|
||||||
if(directbuffer == NULL) {
|
|
||||||
Error("Failed requesting writeable buffer for the captured image.");
|
|
||||||
return (-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
while ( true ) {
|
while ( true ) {
|
||||||
|
|
||||||
|
// WHY Are we clearing it? Might be something good in it.
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
|
||||||
if ( !rtspThread->isRunning() )
|
if ( !rtspThread->isRunning() )
|
||||||
return (-1);
|
return (-1);
|
||||||
|
|
||||||
|
//Video recording
|
||||||
|
if ( recording.tv_sec ) {
|
||||||
|
// The directory we are recording to is no longer tied to the current event.
|
||||||
|
// Need to re-init the videostore with the correct directory and start recording again
|
||||||
|
// Not sure why we are only doing this on keyframe, al
|
||||||
|
if ( videoStore && (strcmp(oldDirectory, event_file)!=0) ) {
|
||||||
|
//don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...if we store our key frame location with the event will that be enough?
|
||||||
|
Info("Re-starting video storage module");
|
||||||
|
if ( videoStore ) {
|
||||||
|
delete videoStore;
|
||||||
|
videoStore = NULL;
|
||||||
|
}
|
||||||
|
} // end if changed to new event
|
||||||
|
|
||||||
|
if ( ! videoStore ) {
|
||||||
|
//Instantiate the video storage module
|
||||||
|
|
||||||
|
videoStore = new VideoStore((const char *)event_file, "mp4",
|
||||||
|
mFormatContext->streams[mVideoStreamId],
|
||||||
|
mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId],
|
||||||
|
startTime,
|
||||||
|
this->getMonitor() );
|
||||||
|
strcpy(oldDirectory, event_file);
|
||||||
|
} // end if ! videoStore
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if ( videoStore ) {
|
||||||
|
Info("Deleting videoStore instance");
|
||||||
|
delete videoStore;
|
||||||
|
videoStore = NULL;
|
||||||
|
}
|
||||||
|
} // end if recording or not
|
||||||
|
|
||||||
if ( rtspThread->getFrame( buffer ) ) {
|
if ( rtspThread->getFrame( buffer ) ) {
|
||||||
Debug( 3, "Read frame %d bytes", buffer.size() );
|
Debug( 3, "Read frame %d bytes", buffer.size() );
|
||||||
Debug( 4, "Address %p", buffer.head() );
|
Debug( 4, "Address %p", buffer.head() );
|
||||||
Hexdump( 4, buffer.head(), 16 );
|
Hexdump( 4, buffer.head(), 16 );
|
||||||
|
|
||||||
if ( !buffer.size() )
|
if ( !buffer.size() )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
if(mCodecContext->codec_id == AV_CODEC_ID_H264) {
|
if ( mCodecContext->codec_id == AV_CODEC_ID_H264 ) {
|
||||||
// SPS and PPS frames should be saved and appended to IDR frames
|
// SPS and PPS frames should be saved and appended to IDR frames
|
||||||
int nalType = (buffer.head()[3] & 0x1f);
|
int nalType = (buffer.head()[3] & 0x1f);
|
||||||
|
|
||||||
|
@ -425,13 +465,14 @@ int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* even
|
||||||
|
|
||||||
av_init_packet( &packet );
|
av_init_packet( &packet );
|
||||||
|
|
||||||
// Why are we checking for it being the video stream
|
// Keep decoding until a complete frame is had.
|
||||||
if ( packet.stream_index == mVideoStreamId ) {
|
while ( !frameComplete && buffer.size() > 0 ) {
|
||||||
|
packet.data = buffer.head();
|
||||||
while ( !frameComplete && buffer.size() > 0 ) {
|
packet.size = buffer.size();
|
||||||
packet.data = buffer.head();
|
|
||||||
packet.size = buffer.size();
|
|
||||||
|
|
||||||
|
// Why are we checking for it being the video stream? Because it might be audio or something else.
|
||||||
|
// Um... we just initialized packet... we can't be testing for what it is yet....
|
||||||
|
if ( packet.stream_index == mVideoStreamId ) {
|
||||||
// So this does the decode
|
// So this does the decode
|
||||||
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
|
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
|
||||||
int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet );
|
int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet );
|
||||||
|
@ -449,117 +490,81 @@ int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* even
|
||||||
//Hexdump( 0, buffer.head(), buffer.size() );
|
//Hexdump( 0, buffer.head(), buffer.size() );
|
||||||
|
|
||||||
buffer -= len;
|
buffer -= len;
|
||||||
} // end while get & decode a frame
|
|
||||||
|
|
||||||
if ( frameComplete ) {
|
if ( frameComplete ) {
|
||||||
|
|
||||||
Debug( 3, "Got frame %d", frameCount );
|
Debug( 3, "Got frame %d", frameCount );
|
||||||
|
|
||||||
|
/* Request a writeable buffer of the target image */
|
||||||
|
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
|
||||||
|
if(directbuffer == NULL) {
|
||||||
|
Error("Failed requesting writeable buffer for the captured image.");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||||
av_image_fill_arrays(mFrame->data, mFrame->linesize,
|
av_image_fill_arrays(mFrame->data, mFrame->linesize,
|
||||||
directbuffer, imagePixFormat, width, height, 1);
|
directbuffer, imagePixFormat, width, height, 1);
|
||||||
#else
|
#else
|
||||||
avpicture_fill( (AVPicture *)mFrame, directbuffer,
|
avpicture_fill( (AVPicture *)mFrame, directbuffer,
|
||||||
imagePixFormat, width, height);
|
imagePixFormat, width, height);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//Video recording
|
} // endif frameComplete
|
||||||
if ( recording && !wasRecording ) {
|
|
||||||
//Instantiate the video storage module
|
|
||||||
|
|
||||||
videoStore = new VideoStore((const char *)event_file, "mp4",
|
if ( videoStore ) {
|
||||||
mFormatContext->streams[mVideoStreamId],
|
|
||||||
mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId],
|
|
||||||
startTime,
|
|
||||||
this->getMonitor()->getOrientation() );
|
|
||||||
wasRecording = true;
|
|
||||||
strcpy(oldDirectory, event_file);
|
|
||||||
|
|
||||||
} else if ( !recording && wasRecording && videoStore ) {
|
|
||||||
// Why are we deleting the videostore? Becase for soem reason we are no longer recording? How does that happen?
|
|
||||||
Info("Deleting videoStore instance");
|
|
||||||
delete videoStore;
|
|
||||||
videoStore = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
//The directory we are recording to is no longer tied to the current event. Need to re-init the videostore with the correct directory and start recording again
|
|
||||||
if ( recording && wasRecording && (strcmp(oldDirectory, event_file)!=0) && (packet.flags & AV_PKT_FLAG_KEY) ) {
|
|
||||||
//don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...if we store our key frame location with the event will that be enough?
|
|
||||||
Info("Re-starting video storage module");
|
|
||||||
if ( videoStore ) {
|
|
||||||
delete videoStore;
|
|
||||||
videoStore = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
videoStore = new VideoStore((const char *)event_file, "mp4",
|
|
||||||
mFormatContext->streams[mVideoStreamId],
|
|
||||||
mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId],
|
|
||||||
startTime,
|
|
||||||
this->getMonitor()->getOrientation() );
|
|
||||||
strcpy( oldDirectory, event_file );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( videoStore && recording ) {
|
|
||||||
//Write the packet to our video store
|
//Write the packet to our video store
|
||||||
int ret = videoStore->writeVideoFramePacket(&packet, mFormatContext->streams[mVideoStreamId]);//, &lastKeyframePkt);
|
int ret = videoStore->writeVideoFramePacket(&packet);//, &lastKeyframePkt);
|
||||||
if ( ret < 0 ) {//Less than zero and we skipped a frame
|
if ( ret < 0 ) {//Less than zero and we skipped a frame
|
||||||
av_free_packet( &packet );
|
// Should not
|
||||||
|
zm_av_packet_unref( &packet );
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
} // end if videoStore, so we are recording
|
||||||
|
|
||||||
#if HAVE_LIBSWSCALE
|
#if HAVE_LIBSWSCALE
|
||||||
if(mConvertContext == NULL) {
|
// Why are we re-scaling after writing out the packet?
|
||||||
mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
|
if ( mConvertContext == NULL ) {
|
||||||
|
mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL );
|
||||||
|
|
||||||
if(mConvertContext == NULL)
|
if ( mConvertContext == NULL )
|
||||||
Fatal( "Unable to create conversion context");
|
Fatal( "Unable to create conversion context");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 )
|
if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 )
|
||||||
Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount );
|
Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount );
|
||||||
#else // HAVE_LIBSWSCALE
|
#else // HAVE_LIBSWSCALE
|
||||||
Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" );
|
Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" );
|
||||||
#endif // HAVE_LIBSWSCALE
|
#endif // HAVE_LIBSWSCALE
|
||||||
|
|
||||||
frameCount++;
|
frameCount++;
|
||||||
|
|
||||||
} /* frame complete */
|
|
||||||
} else if ( packet.stream_index == mAudioStreamId ) {
|
} else if ( packet.stream_index == mAudioStreamId ) {
|
||||||
Debug( 4, "Got audio packet" );
|
Debug( 4, "Got audio packet" );
|
||||||
if ( videoStore && recording ) {
|
if ( videoStore && record_audio ) {
|
||||||
if ( record_audio ) {
|
Debug( 4, "Storing Audio packet" );
|
||||||
Debug( 4, "Storing Audio packet" );
|
//Write the packet to our video store
|
||||||
//Write the packet to our video store
|
int ret = videoStore->writeAudioFramePacket( &packet ); //FIXME no relevance of last key frame
|
||||||
int ret = videoStore->writeAudioFramePacket(&packet, mFormatContext->streams[packet.stream_index]); //FIXME no relevance of last key frame
|
if ( ret < 0 ) { //Less than zero and we skipped a frame
|
||||||
if ( ret < 0 ) { //Less than zero and we skipped a frame
|
zm_av_packet_unref( &packet );
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
|
return 0;
|
||||||
av_packet_unref( &packet );
|
|
||||||
#else
|
|
||||||
av_free_packet( &packet );
|
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // end if video or audio packet
|
} // end if video or audio packet
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100)
|
zm_av_packet_unref( &packet );
|
||||||
av_packet_unref( &packet );
|
} // end while ! framecomplete and buffer.size()
|
||||||
#else
|
|
||||||
av_free_packet( &packet );
|
|
||||||
#endif
|
|
||||||
} /* getFrame() */
|
|
||||||
|
|
||||||
if(frameComplete)
|
if(frameComplete)
|
||||||
return (0);
|
return (0);
|
||||||
|
} /* getFrame() */
|
||||||
} // end while true
|
|
||||||
|
} // end while true
|
||||||
|
|
||||||
|
// can never get here.
|
||||||
return (0) ;
|
return (0) ;
|
||||||
} // int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file )
|
} // int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file )
|
||||||
|
|
||||||
int RemoteCameraRtsp::PostCapture()
|
int RemoteCameraRtsp::PostCapture() {
|
||||||
{
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
#endif // HAVE_LIBAVFORMAT
|
#endif // HAVE_LIBAVFORMAT
|
||||||
|
|
|
@ -85,7 +85,7 @@ public:
|
||||||
int PreCapture();
|
int PreCapture();
|
||||||
int Capture( Image &image );
|
int Capture( Image &image );
|
||||||
int PostCapture();
|
int PostCapture();
|
||||||
int CaptureAndRecord( Image &image, bool recording, char* event_directory );
|
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZM_REMOTE_CAMERA_RTSP_H
|
#endif // ZM_REMOTE_CAMERA_RTSP_H
|
||||||
|
|
|
@ -395,6 +395,18 @@ void timespec_diff(struct timespec *start, struct timespec *end, struct timespec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *timeval_to_string( struct timeval tv ) {
|
||||||
|
time_t nowtime;
|
||||||
|
struct tm *nowtm;
|
||||||
|
static char tmbuf[64], buf[64];
|
||||||
|
|
||||||
|
nowtime = tv.tv_sec;
|
||||||
|
nowtm = localtime(&nowtime);
|
||||||
|
strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm);
|
||||||
|
snprintf(buf, sizeof buf, "%s.%06ld", tmbuf, tv.tv_usec);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
std::string UriDecode( const std::string &encoded ) {
|
std::string UriDecode( const std::string &encoded ) {
|
||||||
#ifdef HAVE_LIBCURL
|
#ifdef HAVE_LIBCURL
|
||||||
CURL *curl = curl_easy_init();
|
CURL *curl = curl_easy_init();
|
||||||
|
|
|
@ -61,6 +61,7 @@ void hwcaps_detect();
|
||||||
extern unsigned int sseversion;
|
extern unsigned int sseversion;
|
||||||
extern unsigned int neonversion;
|
extern unsigned int neonversion;
|
||||||
|
|
||||||
|
char *timeval_to_string( struct timeval tv );
|
||||||
std::string UriDecode( const std::string &encoded );
|
std::string UriDecode( const std::string &encoded );
|
||||||
|
|
||||||
#endif // ZM_UTILS_H
|
#endif // ZM_UTILS_H
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
//
|
|
||||||
// ZoneMinder Video Storage Implementation
|
// ZoneMinder Video Storage Implementation
|
||||||
// Written by Chris Wiggins
|
// Written by Chris Wiggins
|
||||||
// http://chriswiggins.co.nz
|
// http://chriswiggins.co.nz
|
||||||
|
@ -29,18 +28,19 @@
|
||||||
#include "zm_videostore.h"
|
#include "zm_videostore.h"
|
||||||
|
|
||||||
extern "C"{
|
extern "C"{
|
||||||
#include "libavutil/time.h"
|
#include "libavutil/time.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoStore::VideoStore(const char *filename_in, const char *format_in,
|
VideoStore::VideoStore(const char *filename_in, const char *format_in,
|
||||||
AVStream *input_st,
|
AVStream *p_video_input_stream,
|
||||||
AVStream *inpaud_st,
|
AVStream *p_audio_input_stream,
|
||||||
int64_t nStartTime,
|
int64_t nStartTime,
|
||||||
Monitor::Orientation orientation
|
Monitor * monitor
|
||||||
) {
|
) {
|
||||||
|
video_input_stream = p_video_input_stream;
|
||||||
|
audio_input_stream = p_audio_input_stream;
|
||||||
|
|
||||||
AVDictionary *pmetadata = NULL;
|
video_input_context = video_input_stream->codec;
|
||||||
int dsr;
|
|
||||||
|
|
||||||
//store inputs in variables local to class
|
//store inputs in variables local to class
|
||||||
filename = filename_in;
|
filename = filename_in;
|
||||||
|
@ -49,11 +49,10 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
|
||||||
keyframeMessage = false;
|
keyframeMessage = false;
|
||||||
keyframeSkipNumber = 0;
|
keyframeSkipNumber = 0;
|
||||||
|
|
||||||
Info("Opening video storage stream %s format: %d\n", filename, format);
|
Info("Opening video storage stream %s format: %s\n", filename, format);
|
||||||
|
|
||||||
//Init everything we need
|
//Init everything we need, shouldn't have to do this, ffmpeg_camera or something else will call it.
|
||||||
int ret;
|
//av_register_all();
|
||||||
av_register_all();
|
|
||||||
|
|
||||||
ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename);
|
ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename);
|
||||||
if ( ret < 0 ) {
|
if ( ret < 0 ) {
|
||||||
|
@ -62,6 +61,8 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
|
||||||
filename,
|
filename,
|
||||||
av_make_error_string(ret).c_str()
|
av_make_error_string(ret).c_str()
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
Debug(2, "Success alocateing output context");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Couldn't deduce format from filename, trying from format name
|
//Couldn't deduce format from filename, trying from format name
|
||||||
|
@ -72,90 +73,148 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
|
||||||
" could not be assigned based on filename or format %s",
|
" could not be assigned based on filename or format %s",
|
||||||
filename, format);
|
filename, format);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Debug(2, "Success alocateing output context");
|
||||||
}
|
}
|
||||||
|
|
||||||
dsr = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0);
|
AVDictionary *pmetadata = NULL;
|
||||||
|
int dsr = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0);
|
||||||
if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ );
|
if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ );
|
||||||
|
|
||||||
oc->metadata = pmetadata;
|
oc->metadata = pmetadata;
|
||||||
|
|
||||||
fmt = oc->oformat;
|
output_format = oc->oformat;
|
||||||
|
|
||||||
video_st = avformat_new_stream(oc, (AVCodec *)input_st->codec->codec);
|
video_output_stream = avformat_new_stream(oc, (AVCodec*)video_input_context->codec);
|
||||||
if (!video_st) {
|
if (!video_output_stream) {
|
||||||
Fatal("Unable to create video out stream\n");
|
Fatal("Unable to create video out stream\n");
|
||||||
|
} else {
|
||||||
|
Debug(2, "Success creating video out stream" );
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = avcodec_copy_context(video_st->codec, input_st->codec);
|
video_output_context = video_output_stream->codec;
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0)
|
||||||
|
Debug(2, "setting parameters");
|
||||||
|
ret = avcodec_parameters_to_context( video_output_context, video_input_stream->codecpar );
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
Error( "Could not initialize stream parameteres");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Debug(2, "Success getting parameters");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ret = avcodec_copy_context(video_output_context, video_input_context );
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
Fatal("Unable to copy input video context to output video context "
|
Fatal("Unable to copy input video context to output video context %s\n",
|
||||||
"%s\n", av_make_error_string(ret).c_str());
|
av_make_error_string(ret).c_str());
|
||||||
|
} else {
|
||||||
|
Debug(3, "Success copying context" );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Just copy them from the input, no reason to choose different
|
||||||
|
video_output_context->time_base = video_input_context->time_base;
|
||||||
|
video_output_stream->time_base = video_input_stream->time_base;
|
||||||
|
|
||||||
|
Debug(3, "Time bases: VIDEO input stream (%d/%d) input codec: (%d/%d) output stream: (%d/%d) output codec (%d/%d)",
|
||||||
|
video_input_stream->time_base.num,
|
||||||
|
video_input_stream->time_base.den,
|
||||||
|
video_input_context->time_base.num,
|
||||||
|
video_input_context->time_base.den,
|
||||||
|
video_output_stream->time_base.num,
|
||||||
|
video_output_stream->time_base.den,
|
||||||
|
video_output_context->time_base.num,
|
||||||
|
video_output_context->time_base.den
|
||||||
|
);
|
||||||
|
|
||||||
|
// WHY?
|
||||||
|
//video_output_context->codec_tag = 0;
|
||||||
|
if (!video_output_context->codec_tag) {
|
||||||
|
Debug(2, "No codec_tag");
|
||||||
|
if (! oc->oformat->codec_tag
|
||||||
|
|| av_codec_get_id (oc->oformat->codec_tag, video_input_context->codec_tag) == video_output_context->codec_id
|
||||||
|
|| av_codec_get_tag(oc->oformat->codec_tag, video_input_context->codec_id) <= 0) {
|
||||||
|
Warning("Setting codec tag");
|
||||||
|
video_output_context->codec_tag = video_input_context->codec_tag;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( video_st->sample_aspect_ratio.den != video_st->codec->sample_aspect_ratio.den ) {
|
|
||||||
Warning("Fixingample_aspect_ratio.den");
|
|
||||||
video_st->sample_aspect_ratio.den = video_st->codec->sample_aspect_ratio.den;
|
|
||||||
}
|
|
||||||
if ( video_st->sample_aspect_ratio.num != input_st->codec->sample_aspect_ratio.num ) {
|
|
||||||
Warning("Fixingample_aspect_ratio.num");
|
|
||||||
video_st->sample_aspect_ratio.num = input_st->codec->sample_aspect_ratio.num;
|
|
||||||
}
|
|
||||||
if ( video_st->codec->codec_id != input_st->codec->codec_id ) {
|
|
||||||
Warning("Fixing video_st->codec->codec_id");
|
|
||||||
video_st->codec->codec_id = input_st->codec->codec_id;
|
|
||||||
}
|
|
||||||
if ( ! video_st->codec->time_base.num ) {
|
|
||||||
Warning("video_st->codec->time_base.num is not set%d/%d. Fixing by setting it to 1", video_st->codec->time_base.num, video_st->codec->time_base.den);
|
|
||||||
Warning("video_st->codec->time_base.num is not set%d/%d. Fixing by setting it to 1", video_st->time_base.num, video_st->time_base.den);
|
|
||||||
video_st->codec->time_base.num = video_st->time_base.num;
|
|
||||||
video_st->codec->time_base.den = video_st->time_base.den;
|
|
||||||
}
|
|
||||||
|
|
||||||
video_st->codec->codec_tag = 0;
|
|
||||||
if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
|
if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||||
video_st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
video_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Monitor::Orientation orientation = monitor->getOrientation();
|
||||||
if ( orientation ) {
|
if ( orientation ) {
|
||||||
if ( orientation == Monitor::ROTATE_0 ) {
|
if ( orientation == Monitor::ROTATE_0 ) {
|
||||||
|
|
||||||
} else if ( orientation == Monitor::ROTATE_90 ) {
|
} else if ( orientation == Monitor::ROTATE_90 ) {
|
||||||
dsr = av_dict_set( &video_st->metadata, "rotate", "90", 0);
|
dsr = av_dict_set( &video_output_stream->metadata, "rotate", "90", 0);
|
||||||
if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ );
|
if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ );
|
||||||
} else if ( orientation == Monitor::ROTATE_180 ) {
|
} else if ( orientation == Monitor::ROTATE_180 ) {
|
||||||
dsr = av_dict_set( &video_st->metadata, "rotate", "180", 0);
|
dsr = av_dict_set( &video_output_stream->metadata, "rotate", "180", 0);
|
||||||
if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ );
|
if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ );
|
||||||
} else if ( orientation == Monitor::ROTATE_270 ) {
|
} else if ( orientation == Monitor::ROTATE_270 ) {
|
||||||
dsr = av_dict_set( &video_st->metadata, "rotate", "270", 0);
|
dsr = av_dict_set( &video_output_stream->metadata, "rotate", "270", 0);
|
||||||
if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ );
|
if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ );
|
||||||
} else {
|
} else {
|
||||||
Warning( "Unsupported Orientation(%d)", orientation );
|
Warning( "Unsupported Orientation(%d)", orientation );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audio_output_codec = NULL;
|
||||||
|
audio_input_context = NULL;
|
||||||
|
audio_output_stream = NULL;
|
||||||
|
#ifdef HAVE_LIBAVRESAMPLE
|
||||||
|
resample_context = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (inpaud_st) {
|
if (audio_input_stream) {
|
||||||
audio_st = avformat_new_stream(oc, inpaud_st->codec->codec);
|
audio_input_context = audio_input_stream->codec;
|
||||||
if (!audio_st) {
|
|
||||||
Error("Unable to create audio out stream\n");
|
if ( audio_input_context->codec_id != AV_CODEC_ID_AAC ) {
|
||||||
audio_st = NULL;
|
static char error_buffer[256];
|
||||||
|
avcodec_string(error_buffer, sizeof(error_buffer), audio_input_context, 0 );
|
||||||
|
Debug(3, "Got something other than AAC (%s)", error_buffer );
|
||||||
|
if ( ! setup_resampler() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ret = avcodec_copy_context(audio_st->codec, inpaud_st->codec);
|
Debug(3, "Got AAC" );
|
||||||
if (ret < 0) {
|
|
||||||
Fatal("Unable to copy audio context %s\n", av_make_error_string(ret).c_str());
|
audio_output_stream = avformat_new_stream(oc, (AVCodec*)audio_input_context->codec);
|
||||||
}
|
if ( ! audio_output_stream ) {
|
||||||
audio_st->codec->codec_tag = 0;
|
Error("Unable to create audio out stream\n");
|
||||||
|
audio_output_stream = NULL;
|
||||||
|
} else {
|
||||||
|
audio_output_context = audio_output_stream->codec;
|
||||||
|
|
||||||
|
ret = avcodec_copy_context(audio_output_context, audio_input_context);
|
||||||
|
if (ret < 0) {
|
||||||
|
Error("Unable to copy audio context %s\n", av_make_error_string(ret).c_str());
|
||||||
|
audio_output_stream = NULL;
|
||||||
|
} else {
|
||||||
|
audio_output_context->codec_tag = 0;
|
||||||
|
if ( audio_output_context->channels > 1 ) {
|
||||||
|
Warning("Audio isn't mono, changing it.");
|
||||||
|
audio_output_context->channels = 1;
|
||||||
|
} else {
|
||||||
|
Debug(3, "Audio is mono");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end if audio_output_stream
|
||||||
|
} // end if is AAC
|
||||||
|
|
||||||
|
if ( audio_output_stream ) {
|
||||||
if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
|
if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||||
audio_st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
audio_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Debug(3, "No Audio output stream");
|
} // end if audio_input_stream
|
||||||
audio_st = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* open the output file, if needed */
|
/* open the output file, if needed */
|
||||||
if (!(fmt->flags & AVFMT_NOFILE)) {
|
if (!(output_format->flags & AVFMT_NOFILE)) {
|
||||||
ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,NULL,NULL);
|
ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,NULL,NULL);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
Fatal("Could not open output file '%s': %s\n", filename,
|
Fatal("Could not open output file '%s': %s\n", filename,
|
||||||
|
@ -163,33 +222,84 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0);
|
|
||||||
//if ((ret = avformat_write_header(ctx, &opts)) < 0) {
|
|
||||||
//}
|
|
||||||
//os->ctx_inited = 1;
|
//os->ctx_inited = 1;
|
||||||
//avio_flush(ctx->pb);
|
//avio_flush(ctx->pb);
|
||||||
//av_dict_free(&opts);
|
//av_dict_free(&opts);
|
||||||
|
zm_dump_stream_format( oc, 0, 0, 1 );
|
||||||
|
if ( audio_output_stream )
|
||||||
|
zm_dump_stream_format( oc, 1, 0, 1 );
|
||||||
|
|
||||||
/* Write the stream header, if any. */
|
AVDictionary * opts = NULL;
|
||||||
ret = avformat_write_header(oc, NULL);
|
//av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0);
|
||||||
|
//av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0);
|
||||||
|
//av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", 0);
|
||||||
|
if ((ret = avformat_write_header(oc, &opts)) < 0) {
|
||||||
|
Warning("Unable to set movflags to frag_custom+dash+delay_moov");
|
||||||
|
/* Write the stream header, if any. */
|
||||||
|
ret = avformat_write_header(oc, NULL);
|
||||||
|
} else if (av_dict_count(opts) != 0) {
|
||||||
|
Warning("some options not set\n");
|
||||||
|
}
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
zm_dump_stream_format( oc, 0, 0, 1 );
|
Error("Error occurred when writing output file header to %s: %s\n",
|
||||||
Fatal("Error occurred when writing output file header to %s: %s\n",
|
|
||||||
filename,
|
filename,
|
||||||
av_make_error_string(ret).c_str());
|
av_make_error_string(ret).c_str());
|
||||||
}
|
}
|
||||||
|
if ( opts )
|
||||||
|
av_dict_free(&opts);
|
||||||
|
|
||||||
prevDts = 0;
|
video_last_pts = 0;
|
||||||
startPts = 0;
|
video_last_dts = 0;
|
||||||
startDts = 0;
|
audio_last_pts = 0;
|
||||||
filter_in_rescale_delta_last = AV_NOPTS_VALUE;
|
audio_last_dts = 0;
|
||||||
|
previous_pts = 0;
|
||||||
|
previous_dts = 0;
|
||||||
|
|
||||||
startTime=av_gettime()-nStartTime;//oc->start_time;
|
|
||||||
Info("VideoStore startTime=%d\n",startTime);
|
|
||||||
} // VideoStore::VideoStore
|
} // VideoStore::VideoStore
|
||||||
|
|
||||||
|
|
||||||
VideoStore::~VideoStore(){
|
VideoStore::~VideoStore(){
|
||||||
|
if ( audio_output_codec ) {
|
||||||
|
// Do we need to flush the outputs? I have no idea.
|
||||||
|
AVPacket pkt;
|
||||||
|
int got_packet;
|
||||||
|
av_init_packet(&pkt);
|
||||||
|
pkt.data = NULL;
|
||||||
|
pkt.size = 0;
|
||||||
|
int64_t size;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0)
|
||||||
|
ret = avcodec_receive_packet( audio_output_context, &pkt );
|
||||||
|
#else
|
||||||
|
ret = avcodec_encode_audio2( audio_output_context, &pkt, NULL, &got_packet );
|
||||||
|
#endif
|
||||||
|
if (ret < 0) {
|
||||||
|
Error("ERror encoding audio while flushing");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Debug(1, "Have audio encoder, need to flush it's output" );
|
||||||
|
size += pkt.size;
|
||||||
|
if (!got_packet) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration );
|
||||||
|
if (pkt.pts != AV_NOPTS_VALUE)
|
||||||
|
pkt.pts = av_rescale_q(pkt.pts, audio_output_context->time_base, audio_output_stream->time_base);
|
||||||
|
if (pkt.dts != AV_NOPTS_VALUE)
|
||||||
|
pkt.dts = av_rescale_q(pkt.dts, audio_output_context->time_base, audio_output_stream->time_base);
|
||||||
|
if (pkt.duration > 0)
|
||||||
|
pkt.duration = av_rescale_q(pkt.duration, audio_output_context->time_base, audio_output_stream->time_base);
|
||||||
|
Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration );
|
||||||
|
pkt.stream_index = audio_output_stream->index;
|
||||||
|
av_interleaved_write_frame( oc, &pkt );
|
||||||
|
zm_av_packet_unref( &pkt );
|
||||||
|
} // while 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush Queues
|
||||||
|
av_interleaved_write_frame( oc, NULL );
|
||||||
|
|
||||||
/* Write the trailer before close */
|
/* Write the trailer before close */
|
||||||
if ( int rc = av_write_trailer(oc) ) {
|
if ( int rc = av_write_trailer(oc) ) {
|
||||||
Error("Error writing trailer %s", av_err2str( rc ) );
|
Error("Error writing trailer %s", av_err2str( rc ) );
|
||||||
|
@ -200,15 +310,21 @@ VideoStore::~VideoStore(){
|
||||||
// I wonder if we should be closing the file first.
|
// I wonder if we should be closing the file first.
|
||||||
// I also wonder if we really need to be doing all the context allocation/de-allocation constantly, or whether we can just re-use it. Just do a file open/close/writeheader/etc.
|
// I also wonder if we really need to be doing all the context allocation/de-allocation constantly, or whether we can just re-use it. Just do a file open/close/writeheader/etc.
|
||||||
// What if we were only doing audio recording?
|
// What if we were only doing audio recording?
|
||||||
if ( video_st ) {
|
if ( video_output_stream ) {
|
||||||
avcodec_close(video_st->codec);
|
avcodec_close(video_output_context);
|
||||||
}
|
}
|
||||||
if (audio_st) {
|
if (audio_output_stream) {
|
||||||
avcodec_close(audio_st->codec);
|
avcodec_close(audio_output_context);
|
||||||
|
#ifdef HAVE_LIBAVRESAMPLE
|
||||||
|
if ( resample_context ) {
|
||||||
|
avresample_close( resample_context );
|
||||||
|
avresample_free( &resample_context );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// WHen will be not using a file ?
|
// WHen will be not using a file ?
|
||||||
if (!(fmt->flags & AVFMT_NOFILE)) {
|
if (!(output_format->flags & AVFMT_NOFILE)) {
|
||||||
/* Close the output file. */
|
/* Close the output file. */
|
||||||
if ( int rc = avio_close(oc->pb) ) {
|
if ( int rc = avio_close(oc->pb) ) {
|
||||||
Error("Error closing avio %s", av_err2str( rc ) );
|
Error("Error closing avio %s", av_err2str( rc ) );
|
||||||
|
@ -221,6 +337,198 @@ VideoStore::~VideoStore(){
|
||||||
avformat_free_context(oc);
|
avformat_free_context(oc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VideoStore::setup_resampler() {
|
||||||
|
#ifdef HAVE_LIBAVRESAMPLE
|
||||||
|
static char error_buffer[256];
|
||||||
|
|
||||||
|
audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
|
||||||
|
if ( ! audio_output_codec ) {
|
||||||
|
Error("Could not find codec for AAC");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Debug(2, "Have audio output codec");
|
||||||
|
|
||||||
|
audio_output_stream = avformat_new_stream( oc, audio_output_codec );
|
||||||
|
audio_output_context = audio_output_stream->codec;
|
||||||
|
|
||||||
|
if ( ! audio_output_context ) {
|
||||||
|
Error( "could not allocate codec context for AAC\n");
|
||||||
|
audio_output_stream = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug(2, "Have audio_output_context");
|
||||||
|
|
||||||
|
AVDictionary *opts = NULL;
|
||||||
|
av_dict_set(&opts, "strict", "experimental", 0);
|
||||||
|
|
||||||
|
/* put sample parameters */
|
||||||
|
audio_output_context->bit_rate = audio_input_context->bit_rate;
|
||||||
|
audio_output_context->sample_rate = audio_input_context->sample_rate;
|
||||||
|
audio_output_context->channels = audio_input_context->channels;
|
||||||
|
audio_output_context->channel_layout = audio_input_context->channel_layout;
|
||||||
|
audio_output_context->sample_fmt = audio_input_context->sample_fmt;
|
||||||
|
//audio_output_context->refcounted_frames = 1;
|
||||||
|
|
||||||
|
if (audio_output_codec->supported_samplerates) {
|
||||||
|
int found = 0;
|
||||||
|
for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) {
|
||||||
|
if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) {
|
||||||
|
found = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( found ) {
|
||||||
|
Debug(3, "Sample rate is good");
|
||||||
|
} else {
|
||||||
|
audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0];
|
||||||
|
Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check that the encoder supports s16 pcm input */
|
||||||
|
if (!check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt)) {
|
||||||
|
Debug( 3, "Encoder does not support sample format %s, setting to FLTP",
|
||||||
|
av_get_sample_fmt_name( audio_output_context->sample_fmt));
|
||||||
|
audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP;
|
||||||
|
}
|
||||||
|
|
||||||
|
//audio_output_stream->time_base = audio_input_stream->time_base;
|
||||||
|
audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate };
|
||||||
|
|
||||||
|
Debug(3, "Audio Time bases input stream (%d/%d) input codec: (%d/%d) output_stream (%d/%d) output codec (%d/%d)",
|
||||||
|
audio_input_stream->time_base.num,
|
||||||
|
audio_input_stream->time_base.den,
|
||||||
|
audio_input_context->time_base.num,
|
||||||
|
audio_input_context->time_base.den,
|
||||||
|
audio_output_stream->time_base.num,
|
||||||
|
audio_output_stream->time_base.den,
|
||||||
|
audio_output_context->time_base.num,
|
||||||
|
audio_output_context->time_base.den
|
||||||
|
);
|
||||||
|
|
||||||
|
ret = avcodec_open2(audio_output_context, audio_output_codec, &opts );
|
||||||
|
av_dict_free(&opts);
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
av_strerror(ret, error_buffer, sizeof(error_buffer));
|
||||||
|
Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer );
|
||||||
|
audio_output_codec = NULL;
|
||||||
|
audio_output_context = NULL;
|
||||||
|
audio_output_stream = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d)",
|
||||||
|
audio_output_context->bit_rate,
|
||||||
|
audio_output_context->sample_rate,
|
||||||
|
audio_output_context->channels,
|
||||||
|
audio_output_context->sample_fmt,
|
||||||
|
audio_output_context->channel_layout,
|
||||||
|
audio_output_context->frame_size
|
||||||
|
);
|
||||||
|
|
||||||
|
output_frame_size = audio_output_context->frame_size;
|
||||||
|
/** Create a new frame to store the audio samples. */
|
||||||
|
if (!(input_frame = zm_av_frame_alloc())) {
|
||||||
|
Error("Could not allocate input frame");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a new frame to store the audio samples. */
|
||||||
|
if (!(output_frame = zm_av_frame_alloc())) {
|
||||||
|
Error("Could not allocate output frame");
|
||||||
|
av_frame_free( &input_frame );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the audio resampler
|
||||||
|
resample_context = avresample_alloc_context();
|
||||||
|
if ( ! resample_context ) {
|
||||||
|
Error( "Could not allocate resample context\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some formats (i.e. WAV) do not produce the proper channel layout
|
||||||
|
if ( audio_input_context->channel_layout == 0 ) {
|
||||||
|
Error( "Bad channel layout. Need to set it to mono.\n");
|
||||||
|
av_opt_set_int( resample_context, "in_channel_layout", av_get_channel_layout( "mono" ), 0 );
|
||||||
|
} else {
|
||||||
|
av_opt_set_int( resample_context, "in_channel_layout", audio_input_context->channel_layout, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
av_opt_set_int( resample_context, "in_sample_fmt", audio_input_context->sample_fmt, 0);
|
||||||
|
av_opt_set_int( resample_context, "in_sample_rate", audio_input_context->sample_rate, 0);
|
||||||
|
av_opt_set_int( resample_context, "in_channels", audio_input_context->channels,0);
|
||||||
|
//av_opt_set_int( resample_context, "out_channel_layout", audio_output_context->channel_layout, 0);
|
||||||
|
av_opt_set_int( resample_context, "out_channel_layout", av_get_channel_layout( "mono" ), 0 );
|
||||||
|
av_opt_set_int( resample_context, "out_sample_fmt", audio_output_context->sample_fmt, 0);
|
||||||
|
av_opt_set_int( resample_context, "out_sample_rate", audio_output_context->sample_rate, 0);
|
||||||
|
av_opt_set_int( resample_context, "out_channels", audio_output_context->channels, 0);
|
||||||
|
|
||||||
|
ret = avresample_open( resample_context );
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
Error( "Could not open resample context\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/**
|
||||||
|
* Allocate as many pointers as there are audio channels.
|
||||||
|
* Each pointer will later point to the audio samples of the corresponding
|
||||||
|
* channels (although it may be NULL for interleaved formats).
|
||||||
|
*/
|
||||||
|
if (!( converted_input_samples = (uint8_t *)calloc( audio_output_context->channels, sizeof(*converted_input_samples))) ) {
|
||||||
|
Error( "Could not allocate converted input sample pointers\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Allocate memory for the samples of all channels in one consecutive
|
||||||
|
* block for convenience.
|
||||||
|
*/
|
||||||
|
if ((ret = av_samples_alloc( &converted_input_samples, NULL,
|
||||||
|
audio_output_context->channels,
|
||||||
|
audio_output_context->frame_size,
|
||||||
|
audio_output_context->sample_fmt, 0)) < 0) {
|
||||||
|
Error( "Could not allocate converted input samples (error '%s')\n",
|
||||||
|
av_make_error_string(ret).c_str() );
|
||||||
|
|
||||||
|
av_freep(converted_input_samples);
|
||||||
|
free(converted_input_samples);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
output_frame->nb_samples = audio_output_context->frame_size;
|
||||||
|
output_frame->format = audio_output_context->sample_fmt;
|
||||||
|
output_frame->channel_layout = audio_output_context->channel_layout;
|
||||||
|
|
||||||
|
// The codec gives us the frame size, in samples, we calculate the size of the samples buffer in bytes
|
||||||
|
unsigned int audioSampleBuffer_size = av_samples_get_buffer_size( NULL, audio_output_context->channels, audio_output_context->frame_size, audio_output_context->sample_fmt, 0 );
|
||||||
|
converted_input_samples = (uint8_t*) av_malloc( audioSampleBuffer_size );
|
||||||
|
|
||||||
|
if ( !converted_input_samples ) {
|
||||||
|
Error( "Could not allocate converted input sample pointers\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the data pointers in the AVFrame
|
||||||
|
if ( avcodec_fill_audio_frame(
|
||||||
|
output_frame,
|
||||||
|
audio_output_context->channels,
|
||||||
|
audio_output_context->sample_fmt,
|
||||||
|
(const uint8_t*) converted_input_samples,
|
||||||
|
audioSampleBuffer_size, 0 ) < 0 ) {
|
||||||
|
Error( "Could not allocate converted input sample pointers\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
Error("Not built with libavresample library. Cannot do audio conversion to AAC");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void VideoStore::dumpPacket( AVPacket *pkt ){
|
void VideoStore::dumpPacket( AVPacket *pkt ){
|
||||||
char b[10240];
|
char b[10240];
|
||||||
|
@ -233,40 +541,84 @@ void VideoStore::dumpPacket( AVPacket *pkt ){
|
||||||
, pkt->stream_index
|
, pkt->stream_index
|
||||||
, pkt->flags
|
, pkt->flags
|
||||||
, pkt->pos
|
, pkt->pos
|
||||||
, pkt->convergence_duration
|
, pkt->duration
|
||||||
);
|
);
|
||||||
Info("%s:%d:DEBUG: %s", __FILE__, __LINE__, b);
|
Debug(1, "%s:%d:DEBUG: %s", __FILE__, __LINE__, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
int VideoStore::writeVideoFramePacket(AVPacket *ipkt, AVStream *input_st){//, AVPacket *lastKeyframePkt){
|
int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
|
||||||
|
|
||||||
//Debug(3, "before ost_tbcket %d", startTime );
|
|
||||||
//zm_dump_stream_format( oc, ipkt->stream_index, 0, 1 );
|
|
||||||
//Debug(3, "before ost_tbcket %d", startTime );
|
|
||||||
int64_t ost_tb_start_time = av_rescale_q(startTime, AV_TIME_BASE_Q, video_st->time_base);
|
|
||||||
|
|
||||||
AVPacket opkt, safepkt;
|
|
||||||
AVPicture pict;
|
|
||||||
|
|
||||||
av_init_packet(&opkt);
|
av_init_packet(&opkt);
|
||||||
|
|
||||||
|
int duration;
|
||||||
|
|
||||||
//Scale the PTS of the outgoing packet to be the correct time base
|
//Scale the PTS of the outgoing packet to be the correct time base
|
||||||
if (ipkt->pts != AV_NOPTS_VALUE) {
|
if (ipkt->pts != AV_NOPTS_VALUE) {
|
||||||
opkt.pts = av_rescale_q(ipkt->pts-startPts, input_st->time_base, video_st->time_base) - ost_tb_start_time;
|
|
||||||
|
if ( ! video_last_pts ) {
|
||||||
|
// This is the first packet.
|
||||||
|
opkt.pts = 0;
|
||||||
|
Debug(2, "Starting video video_last_pts will become (%d)", ipkt->pts );
|
||||||
|
} else {
|
||||||
|
if ( ipkt->pts < video_last_pts ) {
|
||||||
|
Debug(1, "Resetting video_last_pts from (%d) to (%d)", video_last_pts, ipkt->pts );
|
||||||
|
// wrap around, need to figure out the distance FIXME having this wrong should cause a jump, but then play ok?
|
||||||
|
opkt.pts = previous_pts + av_rescale_q( ipkt->pts, video_input_stream->time_base, video_output_stream->time_base);
|
||||||
|
} else {
|
||||||
|
opkt.pts = previous_pts + av_rescale_q( ipkt->pts - video_last_pts, video_input_stream->time_base, video_output_stream->time_base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug(3, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, video_last_pts );
|
||||||
|
duration = ipkt->pts - video_last_pts;
|
||||||
|
video_last_pts = ipkt->pts;
|
||||||
} else {
|
} else {
|
||||||
|
Debug(3, "opkt.pts = undef");
|
||||||
opkt.pts = AV_NOPTS_VALUE;
|
opkt.pts = AV_NOPTS_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Scale the DTS of the outgoing packet to be the correct time base
|
//Scale the DTS of the outgoing packet to be the correct time base
|
||||||
if(ipkt->dts == AV_NOPTS_VALUE) {
|
|
||||||
opkt.dts = av_rescale_q(input_st->cur_dts-startDts, AV_TIME_BASE_Q, video_st->time_base);
|
// Just because the input stream wraps, doesn't mean the output needs to. Really, if we are limiting ourselves to 10min segments I can't imagine every wrapping in the output. So need to handle input wrap, without causing output wrap.
|
||||||
|
if ( ! video_last_dts ) {
|
||||||
|
// This is the first packet.
|
||||||
|
opkt.dts = 0;
|
||||||
|
Debug(1, "Starting video video_last_dts will become (%d)", ipkt->dts );
|
||||||
|
video_last_dts = ipkt->dts;
|
||||||
} else {
|
} else {
|
||||||
opkt.dts = av_rescale_q(ipkt->dts-startDts, input_st->time_base, video_st->time_base);
|
if ( ipkt->dts == AV_NOPTS_VALUE ) {
|
||||||
|
// why are we using cur_dts instead of packet.dts? I think cur_dts is in AV_TIME_BASE_Q, but ipkt.dts is in video_input_stream->time_base
|
||||||
|
if ( video_input_stream->cur_dts < video_last_dts ) {
|
||||||
|
Debug(1, "Resetting video_last_dts from (%d) to (%d) p.dts was (%d)", video_last_dts, video_input_stream->cur_dts, ipkt->dts );
|
||||||
|
opkt.dts = previous_dts + av_rescale_q(video_input_stream->cur_dts, AV_TIME_BASE_Q, video_output_stream->time_base);
|
||||||
|
} else {
|
||||||
|
opkt.dts = previous_dts + av_rescale_q(video_input_stream->cur_dts - video_last_dts, AV_TIME_BASE_Q, video_output_stream->time_base);
|
||||||
|
}
|
||||||
|
Debug(3, "opkt.dts = %d from video_input_stream->cur_dts(%d) - previus_dts(%d)",
|
||||||
|
opkt.dts, video_input_stream->cur_dts, video_last_dts
|
||||||
|
);
|
||||||
|
video_last_dts = video_input_stream->cur_dts;
|
||||||
|
} else {
|
||||||
|
if ( ipkt->dts < video_last_dts ) {
|
||||||
|
Debug(1, "Resetting video_last_dts from (%d) to (%d)", video_last_dts, ipkt->dts );
|
||||||
|
opkt.dts = previous_dts + av_rescale_q( ipkt->dts, video_input_stream->time_base, video_output_stream->time_base);
|
||||||
|
} else {
|
||||||
|
opkt.dts = previous_dts + av_rescale_q( ipkt->dts - video_last_dts, video_input_stream->time_base, video_output_stream->time_base);
|
||||||
|
}
|
||||||
|
Debug(3, "opkt.dts = %d from ipkt.dts(%d) - previus_dts(%d)",
|
||||||
|
opkt.dts, ipkt->dts, video_last_dts
|
||||||
|
);
|
||||||
|
video_last_dts = ipkt->dts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( opkt.dts > opkt.pts ) {
|
||||||
|
Debug( 1, "opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts );
|
||||||
|
opkt.dts = opkt.pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
opkt.dts -= ost_tb_start_time;
|
if ( ipkt->duration == AV_NOPTS_VALUE ) {
|
||||||
|
opkt.duration = av_rescale_q( duration, video_input_stream->time_base, video_output_stream->time_base);
|
||||||
opkt.duration = av_rescale_q(ipkt->duration, input_st->time_base, video_st->time_base);
|
} else {
|
||||||
|
opkt.duration = av_rescale_q(ipkt->duration, video_input_stream->time_base, video_output_stream->time_base);
|
||||||
|
}
|
||||||
opkt.flags = ipkt->flags;
|
opkt.flags = ipkt->flags;
|
||||||
opkt.pos=-1;
|
opkt.pos=-1;
|
||||||
|
|
||||||
|
@ -274,38 +626,31 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt, AVStream *input_st){//, AV
|
||||||
opkt.size = ipkt->size;
|
opkt.size = ipkt->size;
|
||||||
|
|
||||||
// Some camera have audio on stream 0 and video on stream 1. So when we remove the audio, video stream has to go on 0
|
// Some camera have audio on stream 0 and video on stream 1. So when we remove the audio, video stream has to go on 0
|
||||||
if ( ipkt->stream_index > 0 and ! audio_st ) {
|
if ( ipkt->stream_index > 0 and ! audio_output_stream ) {
|
||||||
Debug(1,"Setting stream index to 0 instead of %d", ipkt->stream_index );
|
Debug(1,"Setting stream index to 0 instead of %d", ipkt->stream_index );
|
||||||
opkt.stream_index = 0;
|
opkt.stream_index = 0;
|
||||||
} else {
|
} else {
|
||||||
opkt.stream_index = ipkt->stream_index;
|
opkt.stream_index = ipkt->stream_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*opkt.flags |= AV_PKT_FLAG_KEY;*/
|
AVPacket safepkt;
|
||||||
|
|
||||||
if (video_st->codec->codec_type == AVMEDIA_TYPE_VIDEO && (fmt->flags & AVFMT_RAWPICTURE)) {
|
|
||||||
/* store AVPicture in AVPacket, as expected by the output format */
|
|
||||||
avpicture_fill(&pict, opkt.data, video_st->codec->pix_fmt, video_st->codec->width, video_st->codec->height);
|
|
||||||
opkt.data = (uint8_t *)&pict;
|
|
||||||
opkt.size = sizeof(AVPicture);
|
|
||||||
opkt.flags |= AV_PKT_FLAG_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(&safepkt, &opkt, sizeof(AVPacket));
|
memcpy(&safepkt, &opkt, sizeof(AVPacket));
|
||||||
|
|
||||||
|
Debug(1, "writing video packet pts(%d) dts(%d) duration(%d)", opkt.pts, opkt.dts, opkt.duration );
|
||||||
if ((opkt.data == NULL)||(opkt.size < 1)) {
|
if ((opkt.data == NULL)||(opkt.size < 1)) {
|
||||||
Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ );
|
Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ );
|
||||||
|
dumpPacket( ipkt);
|
||||||
dumpPacket(&opkt);
|
dumpPacket(&opkt);
|
||||||
|
|
||||||
} else if ((prevDts > 0) && (prevDts >= opkt.dts)) {
|
} else if ((previous_dts > 0) && (previous_dts > opkt.dts)) {
|
||||||
Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, prevDts, opkt.dts);
|
Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, previous_dts, opkt.dts);
|
||||||
prevDts = opkt.dts;
|
previous_dts = opkt.dts;
|
||||||
dumpPacket(&opkt);
|
dumpPacket(&opkt);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
int ret;
|
|
||||||
|
|
||||||
prevDts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance
|
previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance
|
||||||
|
previous_pts = opkt.pts;
|
||||||
ret = av_interleaved_write_frame(oc, &opkt);
|
ret = av_interleaved_write_frame(oc, &opkt);
|
||||||
if(ret<0){
|
if(ret<0){
|
||||||
// There's nothing we can really do if the frame is rejected, just drop it and get on with the next
|
// There's nothing we can really do if the frame is rejected, just drop it and get on with the next
|
||||||
|
@ -314,79 +659,218 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt, AVStream *input_st){//, AV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
av_free_packet(&opkt);
|
zm_av_packet_unref(&opkt);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int VideoStore::writeAudioFramePacket(AVPacket *ipkt, AVStream *input_st){
|
int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) {
|
||||||
|
Debug(4, "writeAudioFrame");
|
||||||
|
|
||||||
if(!audio_st) {
|
if(!audio_output_stream) {
|
||||||
Error("Called writeAudioFramePacket when no audio_st");
|
Debug(1, "Called writeAudioFramePacket when no audio_output_stream");
|
||||||
return -1;//FIXME -ve return codes do not free packet in ffmpeg_camera at the moment
|
return 0;//FIXME -ve return codes do not free packet in ffmpeg_camera at the moment
|
||||||
}
|
}
|
||||||
/*if(!keyframeMessage)
|
|
||||||
return -1;*/
|
|
||||||
//zm_dump_stream_format( oc, ipkt->stream_index, 0, 1 );
|
|
||||||
|
|
||||||
// What is this doing? Getting the time of the start of this video chunk? Does that actually make sense?
|
|
||||||
int64_t ost_tb_start_time = av_rescale_q(startTime, AV_TIME_BASE_Q, audio_st->time_base);
|
|
||||||
|
|
||||||
AVPacket opkt;
|
if ( audio_output_codec ) {
|
||||||
|
#ifdef HAVE_LIBAVRESAMPLE
|
||||||
|
|
||||||
av_init_packet(&opkt);
|
#if 0
|
||||||
Debug(3, "after init packet" );
|
ret = avcodec_send_packet( audio_input_context, ipkt );
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = avcodec_receive_frame( audio_input_context, input_frame );
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Debug(2, "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d) refd(%d)",
|
||||||
|
input_frame->nb_samples,
|
||||||
|
input_frame->format,
|
||||||
|
input_frame->sample_rate,
|
||||||
|
input_frame->channel_layout,
|
||||||
|
audio_output_context->refcounted_frames
|
||||||
|
);
|
||||||
|
|
||||||
|
ret = avcodec_send_frame( audio_output_context, input_frame );
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
av_frame_unref( input_frame );
|
||||||
|
Error("avcodec_send_frame fail(%d), %s codec is open(%d) is_encoder(%d)", ret, av_make_error_string(ret).c_str(),
|
||||||
|
avcodec_is_open( audio_output_context ),
|
||||||
|
av_codec_is_encoder( audio_output_context->codec)
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ret = avcodec_receive_packet( audio_output_context, &opkt );
|
||||||
|
if ( ret < 0 ) {
|
||||||
|
av_frame_unref( input_frame );
|
||||||
|
Error("avcodec_receive_packet fail %s", av_make_error_string(ret).c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
av_frame_unref( input_frame );
|
||||||
|
#else
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the audio frame stored in the packet.
|
||||||
|
* The input audio stream decoder is used to do this.
|
||||||
|
* If we are at the end of the file, pass an empty packet to the decoder
|
||||||
|
* to flush it.
|
||||||
|
*/
|
||||||
|
if ((ret = avcodec_decode_audio4(audio_input_context, input_frame,
|
||||||
|
&data_present, ipkt)) < 0) {
|
||||||
|
Error( "Could not decode frame (error '%s')\n",
|
||||||
|
av_make_error_string(ret).c_str());
|
||||||
|
dumpPacket( ipkt );
|
||||||
|
av_frame_free( &input_frame );
|
||||||
|
zm_av_packet_unref( &opkt );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ( ! data_present ) {
|
||||||
|
Debug(2, "Not ready to transcode a frame yet.");
|
||||||
|
zm_av_packet_unref(&opkt);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int frame_size = input_frame->nb_samples;
|
||||||
|
Debug(4, "Frame size: %d", frame_size );
|
||||||
|
|
||||||
|
// Resample the input into the audioSampleBuffer until we proceed the whole decoded data
|
||||||
|
if ( (ret = avresample_convert( resample_context,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
input_frame->data,
|
||||||
|
0,
|
||||||
|
input_frame->nb_samples )) < 0 ) {
|
||||||
|
Error( "Could not resample frame (error '%s')\n",
|
||||||
|
av_make_error_string(ret).c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( avresample_available( resample_context ) < output_frame->nb_samples ) {
|
||||||
|
Debug(1, "No enough samples yet");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a frame audio data from the resample fifo
|
||||||
|
if ( avresample_read( resample_context, output_frame->data, output_frame->nb_samples ) != output_frame->nb_samples ) {
|
||||||
|
Warning( "Error reading resampled audio: " );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
av_init_packet(&opkt);
|
||||||
|
Debug(5, "after init packet" );
|
||||||
|
|
||||||
|
/** Set a timestamp based on the sample rate for the container. */
|
||||||
|
//output_frame->pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base );
|
||||||
|
|
||||||
|
// convert the packet to the codec timebase from the stream timebase
|
||||||
|
//Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts,
|
||||||
|
//av_frame_get_best_effort_timestamp(output_frame)
|
||||||
|
//);
|
||||||
|
/**
|
||||||
|
* Encode the audio frame and store it in the temporary packet.
|
||||||
|
* The output audio stream encoder is used to do this.
|
||||||
|
*/
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0)
|
||||||
|
if (( ret = avcodec_receive_packet( audio_output_context, &opkt )) < 0 ) {
|
||||||
|
#else
|
||||||
|
if (( ret = avcodec_encode_audio2( audio_output_context, &opkt, output_frame, &data_present )) < 0) {
|
||||||
|
#endif
|
||||||
|
Error( "Could not encode frame (error '%s')",
|
||||||
|
av_make_error_string(ret).c_str());
|
||||||
|
zm_av_packet_unref(&opkt);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ( ! data_present ) {
|
||||||
|
Debug(2, "Not ready to output a frame yet.");
|
||||||
|
zm_av_packet_unref(&opkt);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
av_init_packet(&opkt);
|
||||||
|
Debug(5, "after init packet" );
|
||||||
|
opkt.data = ipkt->data;
|
||||||
|
opkt.size = ipkt->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PTS is difficult, because of the buffering of the audio packets in the resampler. So we have to do it once we actually have a packet...
|
||||||
|
|
||||||
//Scale the PTS of the outgoing packet to be the correct time base
|
//Scale the PTS of the outgoing packet to be the correct time base
|
||||||
if (ipkt->pts != AV_NOPTS_VALUE) {
|
if ( ipkt->pts != AV_NOPTS_VALUE ) {
|
||||||
Debug(3, "Rescaling output pts");
|
if ( !audio_last_pts ) {
|
||||||
opkt.pts = av_rescale_q(ipkt->pts-startPts, input_st->time_base, audio_st->time_base) - ost_tb_start_time;
|
opkt.pts = 0;
|
||||||
|
} else {
|
||||||
|
if ( audio_last_pts > ipkt->pts ) {
|
||||||
|
Debug(1, "Resetting audeo_start_pts from (%d) to (%d)", audio_last_pts, ipkt->pts );
|
||||||
|
}
|
||||||
|
opkt.pts = previous_pts + av_rescale_q(ipkt->pts - audio_last_pts, audio_input_stream->time_base, audio_output_stream->time_base);
|
||||||
|
Debug(2, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, audio_last_pts );
|
||||||
|
}
|
||||||
|
audio_last_pts = ipkt->pts;
|
||||||
} else {
|
} else {
|
||||||
Debug(3, "Setting output pts to AV_NOPTS_VALUE");
|
Debug(2, "opkt.pts = undef");
|
||||||
opkt.pts = AV_NOPTS_VALUE;
|
opkt.pts = AV_NOPTS_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Scale the DTS of the outgoing packet to be the correct time base
|
//Scale the DTS of the outgoing packet to be the correct time base
|
||||||
if(ipkt->dts == AV_NOPTS_VALUE) {
|
if ( ! audio_last_dts ) {
|
||||||
Debug(4, "ipkt->dts == AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts );
|
opkt.dts = 0;
|
||||||
opkt.dts = av_rescale_q(input_st->cur_dts-startDts, AV_TIME_BASE_Q, audio_st->time_base);
|
|
||||||
Debug(4, "ipkt->dts == AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts );
|
|
||||||
} else {
|
} else {
|
||||||
Debug(4, "ipkt->dts != AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts );
|
if( ipkt->dts == AV_NOPTS_VALUE ) {
|
||||||
opkt.dts = av_rescale_q(ipkt->dts-startDts, input_st->time_base, audio_st->time_base);
|
// So if the input has no dts assigned... still need an output dts... so we use cur_dts?
|
||||||
Debug(4, "ipkt->dts != AV_NOPTS_VALUE %d to %d", AV_NOPTS_VALUE, opkt.dts );
|
|
||||||
|
if ( audio_last_dts > audio_input_stream->cur_dts ) {
|
||||||
|
Debug(1, "Resetting audio_last_pts from (%d) to cur_dts (%d)", audio_last_dts, audio_input_stream->cur_dts );
|
||||||
|
opkt.dts = previous_dts + av_rescale_q( audio_input_stream->cur_dts, AV_TIME_BASE_Q, audio_output_stream->time_base);
|
||||||
|
} else {
|
||||||
|
opkt.dts = previous_dts + av_rescale_q( audio_input_stream->cur_dts - audio_last_dts, AV_TIME_BASE_Q, audio_output_stream->time_base);
|
||||||
|
}
|
||||||
|
audio_last_dts = audio_input_stream->cur_dts;
|
||||||
|
Debug(2, "opkt.dts = %d from video_input_stream->cur_dts(%d) - last_dts(%d)", opkt.dts, audio_input_stream->cur_dts, audio_last_dts );
|
||||||
|
} else {
|
||||||
|
if ( audio_last_dts > ipkt->dts ) {
|
||||||
|
Debug(1, "Resetting audio_last_dts from (%d) to (%d)", audio_last_dts, ipkt->dts );
|
||||||
|
opkt.dts = previous_dts + av_rescale_q(ipkt->dts, audio_input_stream->time_base, audio_output_stream->time_base);
|
||||||
|
} else {
|
||||||
|
opkt.dts = previous_dts + av_rescale_q(ipkt->dts - audio_last_dts, audio_input_stream->time_base, audio_output_stream->time_base);
|
||||||
|
}
|
||||||
|
Debug(2, "opkt.dts = %d from ipkt->dts(%d) - last_dts(%d)", opkt.dts, ipkt->dts, audio_last_dts );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
opkt.dts -= ost_tb_start_time;
|
if ( opkt.dts > opkt.pts ) {
|
||||||
|
Debug(1,"opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts );
|
||||||
// Seems like it would be really weird for the codec type to NOT be audiu
|
opkt.dts = opkt.pts;
|
||||||
if (audio_st->codec->codec_type == AVMEDIA_TYPE_AUDIO && ipkt->dts != AV_NOPTS_VALUE) {
|
|
||||||
Debug( 4, "code is audio, dts != AV_NOPTS_VALUE " );
|
|
||||||
int duration = av_get_audio_frame_duration(input_st->codec, ipkt->size);
|
|
||||||
if(!duration)
|
|
||||||
duration = input_st->codec->frame_size;
|
|
||||||
|
|
||||||
//FIXME where to get filter_in_rescale_delta_last
|
|
||||||
//FIXME av_rescale_delta doesn't exist in ubuntu vivid libavtools
|
|
||||||
opkt.dts = opkt.pts = av_rescale_delta(input_st->time_base, ipkt->dts,
|
|
||||||
(AVRational){1, input_st->codec->sample_rate}, duration, &filter_in_rescale_delta_last,
|
|
||||||
audio_st->time_base) - ost_tb_start_time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opkt.duration = av_rescale_q(ipkt->duration, input_st->time_base, audio_st->time_base);
|
// I wonder if we could just use duration instead of all the hoop jumping above?
|
||||||
opkt.pos=-1;
|
opkt.duration = av_rescale_q(ipkt->duration, audio_input_stream->time_base, audio_output_stream->time_base);
|
||||||
|
|
||||||
|
// pkt.pos: byte position in stream, -1 if unknown
|
||||||
|
opkt.pos = -1;
|
||||||
opkt.flags = ipkt->flags;
|
opkt.flags = ipkt->flags;
|
||||||
|
|
||||||
opkt.data = ipkt->data;
|
|
||||||
opkt.size = ipkt->size;
|
|
||||||
opkt.stream_index = ipkt->stream_index;
|
opkt.stream_index = ipkt->stream_index;
|
||||||
|
Debug(2, "Stream index is %d", opkt.stream_index );
|
||||||
|
|
||||||
int ret;
|
AVPacket safepkt;
|
||||||
|
memcpy(&safepkt, &opkt, sizeof(AVPacket));
|
||||||
ret = av_interleaved_write_frame(oc, &opkt);
|
ret = av_interleaved_write_frame(oc, &opkt);
|
||||||
if(ret!=0){
|
if(ret!=0){
|
||||||
Fatal("Error encoding audio frame packet: %s\n", av_make_error_string(ret).c_str());
|
Error("Error writing audio frame packet: %s\n", av_make_error_string(ret).c_str());
|
||||||
|
dumpPacket(&safepkt);
|
||||||
|
} else {
|
||||||
|
Debug(2,"Success writing audio frame" );
|
||||||
}
|
}
|
||||||
Debug(4,"Success writing audio frame" );
|
zm_av_packet_unref(&opkt);
|
||||||
av_free_packet(&opkt);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
} // end int VideoStore::writeAudioFramePacket( AVPacket *ipkt )
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
#define ZM_VIDEOSTORE_H
|
#define ZM_VIDEOSTORE_H
|
||||||
|
|
||||||
#include "zm_ffmpeg.h"
|
#include "zm_ffmpeg.h"
|
||||||
|
extern "C" {
|
||||||
|
#include "libavutil/audio_fifo.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBAVRESAMPLE
|
||||||
|
#include "libavresample/avresample.h"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#if HAVE_LIBAVCODEC
|
#if HAVE_LIBAVCODEC
|
||||||
|
|
||||||
|
@ -9,45 +16,67 @@
|
||||||
|
|
||||||
class VideoStore {
|
class VideoStore {
|
||||||
private:
|
private:
|
||||||
|
unsigned int packets_written;
|
||||||
|
|
||||||
AVOutputFormat *fmt;
|
AVOutputFormat *output_format;
|
||||||
AVFormatContext *oc;
|
AVFormatContext *oc;
|
||||||
AVStream *video_st;
|
AVStream *video_output_stream;
|
||||||
AVStream *audio_st;
|
AVStream *audio_output_stream;
|
||||||
|
AVCodecContext *video_output_context;
|
||||||
|
|
||||||
|
AVStream *video_input_stream;
|
||||||
|
AVStream *audio_input_stream;
|
||||||
|
|
||||||
|
// Move this into the object so that we aren't constantly allocating/deallocating it on the stack
|
||||||
|
AVPacket opkt;
|
||||||
|
// we are transcoding
|
||||||
|
AVFrame *input_frame;
|
||||||
|
AVFrame *output_frame;
|
||||||
|
|
||||||
|
AVCodecContext *video_input_context;
|
||||||
|
AVCodecContext *audio_input_context;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
// The following are used when encoding the audio stream to AAC
|
||||||
|
AVCodec *audio_output_codec;
|
||||||
|
AVCodecContext *audio_output_context;
|
||||||
|
int data_present;
|
||||||
|
AVAudioFifo *fifo;
|
||||||
|
int output_frame_size;
|
||||||
|
#ifdef HAVE_LIBAVRESAMPLE
|
||||||
|
AVAudioResampleContext* resample_context;
|
||||||
|
#endif
|
||||||
|
uint8_t *converted_input_samples = NULL;
|
||||||
|
|
||||||
const char *filename;
|
const char *filename;
|
||||||
const char *format;
|
const char *format;
|
||||||
|
|
||||||
bool keyframeMessage;
|
bool keyframeMessage;
|
||||||
int keyframeSkipNumber;
|
int keyframeSkipNumber;
|
||||||
|
|
||||||
int64_t startTime;
|
// These are for input
|
||||||
int64_t startPts;
|
int64_t video_last_pts;
|
||||||
int64_t startDts;
|
int64_t video_last_dts;
|
||||||
int64_t prevDts;
|
int64_t audio_last_pts;
|
||||||
int64_t filter_in_rescale_delta_last;
|
int64_t audio_last_dts;
|
||||||
|
|
||||||
|
// These are for output, should start at zero. We assume they do not wrap because we just aren't going to save files that big.
|
||||||
|
int64_t previous_pts;
|
||||||
|
int64_t previous_dts;
|
||||||
|
|
||||||
|
int64_t filter_in_rescale_delta_last;
|
||||||
|
|
||||||
|
bool setup_resampler();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VideoStore(const char *filename_in, const char *format_in, AVStream *input_st, AVStream *inpaud_st, int64_t nStartTime, Monitor::Orientation p_orientation );
|
VideoStore(const char *filename_in, const char *format_in, AVStream *video_input_stream, AVStream *audio_input_stream, int64_t nStartTime, Monitor * p_monitor );
|
||||||
~VideoStore();
|
~VideoStore();
|
||||||
|
|
||||||
int writeVideoFramePacket(AVPacket *pkt, AVStream *input_st);//, AVPacket *lastKeyframePkt);
|
int writeVideoFramePacket( AVPacket *pkt );
|
||||||
int writeAudioFramePacket(AVPacket *pkt, AVStream *input_st);
|
int writeAudioFramePacket( AVPacket *pkt );
|
||||||
void dumpPacket( AVPacket *pkt );
|
void dumpPacket( AVPacket *pkt );
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
class VideoEvent {
|
|
||||||
public:
|
|
||||||
VideoEvent(unsigned int eid);
|
|
||||||
~VideoEvent();
|
|
||||||
|
|
||||||
int createEventImage(unsigned int fid, char *&pBuff);
|
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned int m_eid;
|
|
||||||
};*/
|
|
||||||
|
|
||||||
#endif //havelibav
|
#endif //havelibav
|
||||||
#endif //zm_videostore_h
|
#endif //zm_videostore_h
|
||||||
|
|
||||||
|
|
|
@ -42,12 +42,6 @@
|
||||||
#cmakedefine HAVE_GNUTLS_GNUTLS_H 1
|
#cmakedefine HAVE_GNUTLS_GNUTLS_H 1
|
||||||
#cmakedefine HAVE_LIBMYSQLCLIENT 1
|
#cmakedefine HAVE_LIBMYSQLCLIENT 1
|
||||||
#cmakedefine HAVE_MYSQL_H 1
|
#cmakedefine HAVE_MYSQL_H 1
|
||||||
#cmakedefine HAVE_LIBX264 1
|
|
||||||
#cmakedefine HAVE_X264_H 1
|
|
||||||
#cmakedefine HAVE_LIBMP4V2 1
|
|
||||||
#cmakedefine HAVE_MP4V2_MP4V2_H 1
|
|
||||||
#cmakedefine HAVE_MP4V2_H 1
|
|
||||||
#cmakedefine HAVE_MP4_H 1
|
|
||||||
#cmakedefine HAVE_LIBAVFORMAT 1
|
#cmakedefine HAVE_LIBAVFORMAT 1
|
||||||
#cmakedefine HAVE_LIBAVFORMAT_AVFORMAT_H 1
|
#cmakedefine HAVE_LIBAVFORMAT_AVFORMAT_H 1
|
||||||
#cmakedefine HAVE_LIBAVCODEC 1
|
#cmakedefine HAVE_LIBAVCODEC 1
|
||||||
|
@ -59,8 +53,16 @@
|
||||||
#cmakedefine HAVE_LIBAVUTIL_MATHEMATICS_H 1
|
#cmakedefine HAVE_LIBAVUTIL_MATHEMATICS_H 1
|
||||||
#cmakedefine HAVE_LIBSWSCALE 1
|
#cmakedefine HAVE_LIBSWSCALE 1
|
||||||
#cmakedefine HAVE_LIBSWSCALE_SWSCALE_H 1
|
#cmakedefine HAVE_LIBSWSCALE_SWSCALE_H 1
|
||||||
|
#cmakedefine HAVE_LIBAVRESAMPLE 1
|
||||||
|
#cmakedefine HAVE_LIBAVRESAMPLE_AVRESAMPLE_H 1
|
||||||
#cmakedefine HAVE_LIBVLC 1
|
#cmakedefine HAVE_LIBVLC 1
|
||||||
#cmakedefine HAVE_VLC_VLC_H 1
|
#cmakedefine HAVE_VLC_VLC_H 1
|
||||||
|
#cmakedefine HAVE_LIBX264 1
|
||||||
|
#cmakedefine HAVE_X264_H 1
|
||||||
|
#cmakedefine HAVE_LIBMP4V2 1
|
||||||
|
#cmakedefine HAVE_MP4_H 1
|
||||||
|
#cmakedefine HAVE_MP4V2_H 1
|
||||||
|
#cmakedefine HAVE_MP4V2_MP4V2_H 1
|
||||||
|
|
||||||
/* Authenication checks */
|
/* Authenication checks */
|
||||||
#cmakedefine HAVE_MD5_OPENSSL 1
|
#cmakedefine HAVE_MD5_OPENSSL 1
|
||||||
|
|
Loading…
Reference in New Issue