/******************************************************************************** Copyright (C) 2012 Hugh Bailey This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. ********************************************************************************/ #include "Main.h" #include #include extern "C" { #include "../x264/x264.h" } void get_x264_log(void *param, int i_level, const char *psz, va_list argptr) { String chi(psz); chi.FindReplace(TEXT("%s"), TEXT("%S")); OSDebugOutva(chi, argptr); chi.FindReplace(TEXT("\r"), TEXT("")); chi.FindReplace(TEXT("\n"), TEXT("")); Logva(chi.Array(), argptr); } struct VideoPacket { List Packet; inline void FreeData() {Packet.Clear();} }; const float baseCRF = 22.0f; class X264Encoder : public VideoEncoder { x264_param_t paramData; x264_t *x264; x264_picture_t picOut; int cur_pts_time; x264_nal_t *pp_nal; int pi_nal; int fps_ms; UINT width, height; String curPreset; bool bFirstFrameProcessed; bool bUseCBR, bUseCFR; List CurrentPackets; List HeaderPacket; INT64 delayOffset; int frameShift; inline void ClearPackets() { for(UINT i=0; iwidth = width; this->height = height; //warning: messing with x264 settings without knowing what they do can seriously screw things up //ratetol //qcomp //paramData.i_frame_reference = 1; //ref=1 //paramData.i_threads = 4; bUseCBR = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("UseCBR")) != 0; this->bUseCFR = bUseCFR; SetBitRateParams(maxBitrate, bufferSize); if(bUseCBR) { paramData.i_nal_hrd = X264_NAL_HRD_CBR; paramData.rc.i_rc_method = X264_RC_ABR; paramData.rc.f_rf_constant = 0.0f; } else { paramData.rc.i_rc_method = X264_RC_CRF; paramData.rc.f_rf_constant = baseCRF+float(10-quality); } paramData.b_vfr_input = !bUseCFR; paramData.i_width = width; paramData.i_height = height; paramData.vui.b_fullrange = 0; //specify full range input levels //paramData.i_keyint_max = fps*4; //keyframe every 4 sec, should make this an option paramData.i_fps_num = fps; paramData.i_fps_den = 1; paramData.i_timebase_num = 1; paramData.i_timebase_den = 1000; //paramData.pf_log = get_x264_log; //paramData.i_log_level = X264_LOG_INFO; BOOL bUseCustomParams = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("UseCustomSettings")); if(bUseCustomParams) { String strCustomParams = AppConfig->GetString(TEXT("Video Encoding"), TEXT("CustomSettings")); strCustomParams.KillSpaces(); if(strCustomParams.IsValid()) { Log(TEXT("Using custom x264 settings: \"%s\""), strCustomParams.Array()); StringList paramList; strCustomParams.GetTokenList(paramList, ' ', FALSE); for(UINT i=0; i &packets, List &packetTypes, DWORD outputTimestamp, int &ctsOffset) { x264_picture_t *picIn = (x264_picture_t*)picInPtr; x264_nal_t *nalOut; int nalNum; packets.Clear(); ClearPackets(); if(x264_encoder_encode(x264, &nalOut, &nalNum, picIn, &picOut) < 0) { AppWarning(TEXT("x264 encode failed")); return false; } if(!bFirstFrameProcessed && nalNum) { delayOffset = -picOut.i_dts; bFirstFrameProcessed = true; } INT64 ts = INT64(outputTimestamp); int timeOffset = int((picOut.i_pts+delayOffset)-ts); if(bUseCFR) { //if CFR's being used, the shift will be insignificant, so just don't bother adjusting audio timeOffset += frameShift; if(nalNum && timeOffset < 0) { frameShift -= timeOffset; timeOffset = 0; } } else { timeOffset += ctsOffset; //dynamically adjust the CTS for the stream if it gets lower than the current value //(thanks to cyrus for suggesting to do this instead of a single shift) if(nalNum && timeOffset < 0) { ctsOffset -= timeOffset; timeOffset = 0; } } //Log(TEXT("dts: %d, pts: %d, timestamp: %d, offset: %d"), picOut.i_dts, picOut.i_pts, outputTimestamp, timeOffset); timeOffset = htonl(timeOffset); BYTE *timeOffsetAddr = ((BYTE*)&timeOffset)+1; VideoPacket *newPacket = NULL; for(int i=0; iPacket); if(bNewPacket) { packetOut.OutputByte((nal.i_type == NAL_SLICE_IDR || nal.i_type == NAL_SEI) ? 0x17 : 0x27); packetOut.OutputByte(1); packetOut.Serialize(timeOffsetAddr, 3); } packetOut.OutputDword(htonl(newPayloadSize)); packetOut.Serialize(nal.p_payload+skipBytes, newPayloadSize); } /*else if(nal.i_type == NAL_SPS) { VideoPacket *newPacket = CurrentPackets.CreateNew(); BufferOutputSerializer headerOut(newPacket->Packet); headerOut.OutputByte(0x17); headerOut.OutputByte(0); headerOut.Serialize(timeOffsetAddr, 3); headerOut.OutputByte(1); headerOut.Serialize(nal.p_payload+5, 3); headerOut.OutputByte(0xff); headerOut.OutputByte(0xe1); headerOut.OutputWord(htons(nal.i_payload-4)); headerOut.Serialize(nal.p_payload+4, nal.i_payload-4); x264_nal_t &pps = nalOut[i+1]; //the PPS always comes after the SPS headerOut.OutputByte(1); headerOut.OutputWord(htons(pps.i_payload-4)); headerOut.Serialize(pps.p_payload+4, pps.i_payload-4); }*/ else continue; switch(nal.i_ref_idc) { case NAL_PRIORITY_DISPOSABLE: packetTypes << PacketType_VideoDisposable; break; case NAL_PRIORITY_LOW: packetTypes << PacketType_VideoLow; break; case NAL_PRIORITY_HIGH: packetTypes << PacketType_VideoHigh; break; case NAL_PRIORITY_HIGHEST: packetTypes << PacketType_VideoHighest; break; } } packets.SetSize(CurrentPackets.Num()); for(UINT i=0; i